@bensandee/tooling 0.36.0 → 0.37.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/README.md +20 -0
- package/dist/bin.mjs +161 -195
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,26 @@ tooling checks:run --add e2e
|
|
|
58
58
|
|
|
59
59
|
The generated `ci:check` script defaults to `pnpm check --skip 'docker:*'` since CI environments typically lack Docker support.
|
|
60
60
|
|
|
61
|
+
### Custom blocks in workflows
|
|
62
|
+
|
|
63
|
+
Generated workflow files are regenerated on each `repo:sync` run. To preserve custom additions (extra steps, environment variables, etc.), wrap them in custom block markers:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
- name: Run checks
|
|
67
|
+
run: pnpm ci:check
|
|
68
|
+
# @tooling:custom
|
|
69
|
+
- name: Upload coverage
|
|
70
|
+
uses: actions/upload-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: coverage
|
|
73
|
+
path: coverage/
|
|
74
|
+
# @tooling:endcustom
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Custom blocks are anchored to the nearest preceding non-blank line. When `repo:sync` regenerates the workflow, it extracts custom blocks from the existing file, generates fresh content, then re-inserts each block after its anchor line. If the anchor line is no longer present (e.g. a step was renamed), the block is appended at the end of the file.
|
|
78
|
+
|
|
79
|
+
To skip a workflow file entirely, add `# @bensandee/tooling:ignore` as the first line — `repo:sync` will leave the file untouched.
|
|
80
|
+
|
|
61
81
|
### Release management
|
|
62
82
|
|
|
63
83
|
| Command | Description |
|
package/dist/bin.mjs
CHANGED
|
@@ -1416,8 +1416,6 @@ function isWorkspacePackage(ctx, packageName) {
|
|
|
1416
1416
|
} catch (_error) {}
|
|
1417
1417
|
return false;
|
|
1418
1418
|
}
|
|
1419
|
-
/** Deps that should not be blindly bumped to latest (version-sensitive). */
|
|
1420
|
-
const UPDATE_EXCLUDE = new Set(["@types/node"]);
|
|
1421
1419
|
/** Add release-strategy-specific devDeps to a deps record. */
|
|
1422
1420
|
function addReleaseDeps(deps, config) {
|
|
1423
1421
|
switch (config.releaseStrategy) {
|
|
@@ -1493,47 +1491,6 @@ function addReleaseDeps(deps, config) {
|
|
|
1493
1491
|
break;
|
|
1494
1492
|
}
|
|
1495
1493
|
}
|
|
1496
|
-
/** Returns the list of pinned devDependency names that the tool would add for a given config. */
|
|
1497
|
-
function getAddedDevDepNames(config) {
|
|
1498
|
-
const deps = { ...ROOT_DEV_DEPS };
|
|
1499
|
-
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
1500
|
-
deps["@bensandee/config"] = "0.9.1";
|
|
1501
|
-
deps["@bensandee/tooling"] = "0.36.0";
|
|
1502
|
-
if (config.formatter === "oxfmt") deps["oxfmt"] = {
|
|
1503
|
-
"@changesets/cli": "2.30.0",
|
|
1504
|
-
"@release-it/bumper": "7.0.5",
|
|
1505
|
-
"@types/node": "24.12.0",
|
|
1506
|
-
"commit-and-tag-version": "12.7.0",
|
|
1507
|
-
"knip": "5.87.0",
|
|
1508
|
-
"lefthook": "2.1.4",
|
|
1509
|
-
"oxfmt": "0.41.0",
|
|
1510
|
-
"oxlint": "1.56.0",
|
|
1511
|
-
"prettier": "3.8.1",
|
|
1512
|
-
"release-it": "19.2.4",
|
|
1513
|
-
"tsdown": "0.21.4",
|
|
1514
|
-
"typescript": "5.9.3",
|
|
1515
|
-
"vitest": "4.1.0",
|
|
1516
|
-
"pnpm": "10.32.1"
|
|
1517
|
-
}["oxfmt"];
|
|
1518
|
-
if (config.formatter === "prettier") deps["prettier"] = {
|
|
1519
|
-
"@changesets/cli": "2.30.0",
|
|
1520
|
-
"@release-it/bumper": "7.0.5",
|
|
1521
|
-
"@types/node": "24.12.0",
|
|
1522
|
-
"commit-and-tag-version": "12.7.0",
|
|
1523
|
-
"knip": "5.87.0",
|
|
1524
|
-
"lefthook": "2.1.4",
|
|
1525
|
-
"oxfmt": "0.41.0",
|
|
1526
|
-
"oxlint": "1.56.0",
|
|
1527
|
-
"prettier": "3.8.1",
|
|
1528
|
-
"release-it": "19.2.4",
|
|
1529
|
-
"tsdown": "0.21.4",
|
|
1530
|
-
"typescript": "5.9.3",
|
|
1531
|
-
"vitest": "4.1.0",
|
|
1532
|
-
"pnpm": "10.32.1"
|
|
1533
|
-
}["prettier"];
|
|
1534
|
-
addReleaseDeps(deps, config);
|
|
1535
|
-
return Object.keys(deps).filter((name) => !UPDATE_EXCLUDE.has(name));
|
|
1536
|
-
}
|
|
1537
1494
|
async function generatePackageJson(ctx) {
|
|
1538
1495
|
const filePath = "package.json";
|
|
1539
1496
|
const existing = ctx.read(filePath);
|
|
@@ -1553,7 +1510,7 @@ async function generatePackageJson(ctx) {
|
|
|
1553
1510
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1554
1511
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1555
1512
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
|
|
1556
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1513
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.37.0";
|
|
1557
1514
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1558
1515
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1559
1516
|
"@changesets/cli": "2.30.0",
|
|
@@ -2405,6 +2362,7 @@ function buildSettings(ctx) {
|
|
|
2405
2362
|
`Bash(${pm} knip)`,
|
|
2406
2363
|
`Bash(${pm} knip *)`,
|
|
2407
2364
|
`Bash(${pm} check)`,
|
|
2365
|
+
`Bash(${pm} ci:check)`,
|
|
2408
2366
|
`Bash(${pm} exec oxlint *)`,
|
|
2409
2367
|
`Bash(${pm} exec knip *)`,
|
|
2410
2368
|
"Bash(git status *)",
|
|
@@ -2578,7 +2536,7 @@ async function generateClaudeSettings(ctx) {
|
|
|
2578
2536
|
}
|
|
2579
2537
|
//#endregion
|
|
2580
2538
|
//#region src/generators/release-it.ts
|
|
2581
|
-
function buildConfig$
|
|
2539
|
+
function buildConfig$3(ci, isMonorepo) {
|
|
2582
2540
|
const config = {
|
|
2583
2541
|
$schema: "https://unpkg.com/release-it/schema/release-it.json",
|
|
2584
2542
|
git: {
|
|
@@ -2606,7 +2564,7 @@ async function generateReleaseIt(ctx) {
|
|
|
2606
2564
|
action: "skipped",
|
|
2607
2565
|
description: "release-it not requested"
|
|
2608
2566
|
};
|
|
2609
|
-
const content = JSON.stringify(buildConfig$
|
|
2567
|
+
const content = JSON.stringify(buildConfig$3(ctx.config.ci, ctx.config.structure === "monorepo"), null, 2) + "\n";
|
|
2610
2568
|
const existing = ctx.read(filePath);
|
|
2611
2569
|
if (existing) {
|
|
2612
2570
|
if (contentEqual(filePath, existing, content)) return {
|
|
@@ -2629,7 +2587,7 @@ async function generateReleaseIt(ctx) {
|
|
|
2629
2587
|
}
|
|
2630
2588
|
//#endregion
|
|
2631
2589
|
//#region src/generators/changesets.ts
|
|
2632
|
-
function buildConfig$
|
|
2590
|
+
function buildConfig$2() {
|
|
2633
2591
|
return {
|
|
2634
2592
|
$schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
2635
2593
|
changelog: "@changesets/cli/changelog",
|
|
@@ -2649,7 +2607,7 @@ async function generateChangesets(ctx) {
|
|
|
2649
2607
|
action: "skipped",
|
|
2650
2608
|
description: "Changesets not requested"
|
|
2651
2609
|
};
|
|
2652
|
-
const content = JSON.stringify(buildConfig$
|
|
2610
|
+
const content = JSON.stringify(buildConfig$2(), null, 2) + "\n";
|
|
2653
2611
|
const existing = ctx.read(filePath);
|
|
2654
2612
|
if (existing) return {
|
|
2655
2613
|
filePath,
|
|
@@ -2836,7 +2794,7 @@ function requiredCommands(formatter) {
|
|
|
2836
2794
|
}
|
|
2837
2795
|
};
|
|
2838
2796
|
}
|
|
2839
|
-
function buildConfig(formatter) {
|
|
2797
|
+
function buildConfig$1(formatter) {
|
|
2840
2798
|
return [
|
|
2841
2799
|
"pre-commit:",
|
|
2842
2800
|
" commands:",
|
|
@@ -2940,7 +2898,7 @@ function cleanPackageJson(ctx, results) {
|
|
|
2940
2898
|
}
|
|
2941
2899
|
async function generateLefthook(ctx) {
|
|
2942
2900
|
const filePath = "lefthook.yml";
|
|
2943
|
-
const content = buildConfig(ctx.config.formatter);
|
|
2901
|
+
const content = buildConfig$1(ctx.config.formatter);
|
|
2944
2902
|
const results = [];
|
|
2945
2903
|
archiveHuskyHooks(ctx, results);
|
|
2946
2904
|
archiveLintStagedConfigs(ctx, results);
|
|
@@ -3081,6 +3039,72 @@ async function generateVscodeSettings(ctx) {
|
|
|
3081
3039
|
return results;
|
|
3082
3040
|
}
|
|
3083
3041
|
//#endregion
|
|
3042
|
+
//#region src/generators/versionrc.ts
|
|
3043
|
+
function buildConfig() {
|
|
3044
|
+
return {
|
|
3045
|
+
header: "# Changelog\n\nAll notable changes to this project will be documented in this file.\n",
|
|
3046
|
+
types: [
|
|
3047
|
+
{
|
|
3048
|
+
type: "feat",
|
|
3049
|
+
section: "Features"
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
type: "fix",
|
|
3053
|
+
section: "Bug Fixes"
|
|
3054
|
+
},
|
|
3055
|
+
{
|
|
3056
|
+
type: "chore",
|
|
3057
|
+
section: "Chores"
|
|
3058
|
+
},
|
|
3059
|
+
{
|
|
3060
|
+
type: "refactor",
|
|
3061
|
+
section: "Refactoring"
|
|
3062
|
+
},
|
|
3063
|
+
{
|
|
3064
|
+
type: "perf",
|
|
3065
|
+
section: "Performance"
|
|
3066
|
+
},
|
|
3067
|
+
{
|
|
3068
|
+
type: "docs",
|
|
3069
|
+
hidden: true
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
type: "style",
|
|
3073
|
+
hidden: true
|
|
3074
|
+
},
|
|
3075
|
+
{
|
|
3076
|
+
type: "test",
|
|
3077
|
+
hidden: true
|
|
3078
|
+
},
|
|
3079
|
+
{
|
|
3080
|
+
type: "ci",
|
|
3081
|
+
hidden: true
|
|
3082
|
+
}
|
|
3083
|
+
],
|
|
3084
|
+
tagPrefix: "v"
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
async function generateVersionrc(ctx) {
|
|
3088
|
+
const filePath = ".versionrc.json";
|
|
3089
|
+
if (ctx.config.releaseStrategy !== "simple") return {
|
|
3090
|
+
filePath,
|
|
3091
|
+
action: "skipped",
|
|
3092
|
+
description: "simple release not requested"
|
|
3093
|
+
};
|
|
3094
|
+
if (ctx.exists(filePath)) return {
|
|
3095
|
+
filePath,
|
|
3096
|
+
action: "skipped",
|
|
3097
|
+
description: "Existing versionrc config preserved"
|
|
3098
|
+
};
|
|
3099
|
+
const content = JSON.stringify(buildConfig(), null, 2) + "\n";
|
|
3100
|
+
ctx.write(filePath, content);
|
|
3101
|
+
return {
|
|
3102
|
+
filePath,
|
|
3103
|
+
action: "created",
|
|
3104
|
+
description: "Generated .versionrc.json"
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
//#endregion
|
|
3084
3108
|
//#region src/generators/pipeline.ts
|
|
3085
3109
|
/** Run all generators sequentially and return their results. */
|
|
3086
3110
|
async function runGenerators(ctx) {
|
|
@@ -3098,6 +3122,7 @@ async function runGenerators(ctx) {
|
|
|
3098
3122
|
results.push(await generateCi(ctx));
|
|
3099
3123
|
results.push(...await generateClaudeSettings(ctx));
|
|
3100
3124
|
results.push(await generateReleaseIt(ctx));
|
|
3125
|
+
results.push(await generateVersionrc(ctx));
|
|
3101
3126
|
results.push(await generateChangesets(ctx));
|
|
3102
3127
|
results.push(await generateReleaseCi(ctx));
|
|
3103
3128
|
results.push(await generateDeployCi(ctx));
|
|
@@ -3110,51 +3135,30 @@ async function runGenerators(ctx) {
|
|
|
3110
3135
|
//#region src/generators/migrate-prompt.ts
|
|
3111
3136
|
/**
|
|
3112
3137
|
* Generate a context-aware AI migration prompt based on what the CLI did.
|
|
3113
|
-
*
|
|
3138
|
+
* Piped into Claude Code via `cat .tooling-migrate.md | claude`.
|
|
3114
3139
|
*/
|
|
3115
3140
|
function generateMigratePrompt(results, config, detected) {
|
|
3116
3141
|
const sections = [];
|
|
3117
3142
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3118
3143
|
sections.push("# Migration Prompt");
|
|
3119
3144
|
sections.push("");
|
|
3120
|
-
sections.push(`_Generated by \`@bensandee/tooling@0.
|
|
3121
|
-
sections.push("");
|
|
3122
|
-
sections.push("The following prompt was generated by `@bensandee/tooling repo:sync`. Paste it into Claude Code or another AI assistant to finish migrating this repository.");
|
|
3145
|
+
sections.push(`_Generated by \`@bensandee/tooling@0.37.0 repo:sync\` on ${timestamp}_`);
|
|
3123
3146
|
sections.push("");
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
sections.push("");
|
|
3128
|
-
const created = results.filter((r) => r.action === "created");
|
|
3129
|
-
const updated = results.filter((r) => r.action === "updated");
|
|
3130
|
-
const skipped = results.filter((r) => r.action === "skipped");
|
|
3131
|
-
const archived = results.filter((r) => r.action === "archived");
|
|
3132
|
-
if (created.length > 0) {
|
|
3133
|
-
sections.push("**Created:**");
|
|
3134
|
-
for (const r of created) sections.push(`- \`${r.filePath}\` — ${r.description}`);
|
|
3135
|
-
sections.push("");
|
|
3136
|
-
}
|
|
3137
|
-
if (updated.length > 0) {
|
|
3138
|
-
sections.push("**Updated:**");
|
|
3139
|
-
for (const r of updated) sections.push(`- \`${r.filePath}\` — ${r.description}`);
|
|
3140
|
-
sections.push("");
|
|
3141
|
-
}
|
|
3142
|
-
if (archived.length > 0) {
|
|
3143
|
-
sections.push("**Archived:**");
|
|
3144
|
-
for (const r of archived) sections.push(`- \`${r.filePath}\` — ${r.description}`);
|
|
3147
|
+
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3148
|
+
if (changed.length > 0) {
|
|
3149
|
+
sections.push("## Files changed");
|
|
3145
3150
|
sections.push("");
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3151
|
+
for (const r of changed) {
|
|
3152
|
+
const label = r.action === "archived" ? "archived" : r.action;
|
|
3153
|
+
sections.push(`- \`${r.filePath}\` (${label}) — ${r.description}`);
|
|
3154
|
+
}
|
|
3150
3155
|
sections.push("");
|
|
3151
3156
|
}
|
|
3152
|
-
|
|
3153
|
-
sections.push("");
|
|
3157
|
+
const tasks = [];
|
|
3154
3158
|
const legacyToRemove = detected.legacyConfigs.filter((legacy) => !(legacy.tool === "prettier" && config.formatter === "prettier"));
|
|
3155
3159
|
if (legacyToRemove.length > 0) {
|
|
3156
|
-
|
|
3157
|
-
|
|
3160
|
+
tasks.push("### Remove legacy tooling");
|
|
3161
|
+
tasks.push("");
|
|
3158
3162
|
for (const legacy of legacyToRemove) {
|
|
3159
3163
|
const replacement = {
|
|
3160
3164
|
eslint: "oxlint",
|
|
@@ -3163,118 +3167,83 @@ function generateMigratePrompt(results, config, detected) {
|
|
|
3163
3167
|
webpack: "tsdown",
|
|
3164
3168
|
rollup: "tsdown"
|
|
3165
3169
|
}[legacy.tool];
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
if (legacy.tool === "eslint")
|
|
3169
|
-
if (legacy.tool === "jest")
|
|
3170
|
+
tasks.push(`- Remove ${legacy.tool} config files (${legacy.files.map((f) => `\`${f}\``).join(", ")}). This project now uses **${replacement}**.`);
|
|
3171
|
+
tasks.push(` - Uninstall ${legacy.tool}-related packages from devDependencies`);
|
|
3172
|
+
if (legacy.tool === "eslint") tasks.push(" - Migrate any custom ESLint rules that don't have oxlint equivalents");
|
|
3173
|
+
if (legacy.tool === "jest") tasks.push(" - Migrate jest-specific test utilities (jest.mock, jest.fn) to vitest equivalents (vi.mock, vi.fn)");
|
|
3170
3174
|
}
|
|
3171
|
-
|
|
3175
|
+
tasks.push("");
|
|
3172
3176
|
}
|
|
3177
|
+
const archived = results.filter((r) => r.action === "archived");
|
|
3173
3178
|
if (archived.length > 0) {
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
for (const r of archived)
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
sections.push("- **Lint configs**: enabled plugin categories (e.g. `jsx-a11y`, `import`, `react`, `nextjs`), custom `plugins` or `overrides`, file-scoped rule blocks");
|
|
3183
|
-
sections.push("- **TypeScript configs**: compiler features like `jsx`, `paths`, `baseUrl`, or `references` that affect build behavior");
|
|
3184
|
-
sections.push("- **Other configs**: feature flags, custom presets, or integrations that go beyond the default template");
|
|
3185
|
-
sections.push("");
|
|
3186
|
-
sections.push("If the old config had capabilities the new one lacks, port them into the new file. Then:");
|
|
3187
|
-
sections.push("");
|
|
3188
|
-
sections.push("1. If the project previously used `husky` and `lint-staged`, remove them from `devDependencies`");
|
|
3189
|
-
sections.push("2. Delete the `.tooling-archived/` directory when migration is complete");
|
|
3190
|
-
sections.push("");
|
|
3179
|
+
tasks.push("### Review archived files");
|
|
3180
|
+
tasks.push("");
|
|
3181
|
+
tasks.push("The originals have been saved to `.tooling-archived/`. For each, diff the old version against the new one and port any capabilities the new file lacks:");
|
|
3182
|
+
tasks.push("");
|
|
3183
|
+
for (const r of archived) tasks.push(`- \`${r.filePath}\` → \`.tooling-archived/${r.filePath}\``);
|
|
3184
|
+
tasks.push("");
|
|
3185
|
+
tasks.push("If the project previously used `husky` and `lint-staged`, remove them from `devDependencies`. Delete `.tooling-archived/` when done.");
|
|
3186
|
+
tasks.push("");
|
|
3191
3187
|
}
|
|
3192
3188
|
const oxlintWasSkipped = results.find((r) => r.filePath === "oxlint.config.ts")?.action === "skipped";
|
|
3193
3189
|
if (detected.hasLegacyOxlintJson) {
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
sections.push("1. Read `.oxlintrc.json` and compare its `rules` against the rules provided by `@bensandee/config/oxlint/recommended` (check `node_modules/@bensandee/config`). Most standard rules are already included in the recommended config.");
|
|
3199
|
-
sections.push("2. If there are any custom rules, overrides, settings, or `jsPlugins` not covered by the recommended config, add them to `oxlint.config.ts` alongside the `extends`.");
|
|
3200
|
-
sections.push("3. Delete `.oxlintrc.json`.");
|
|
3201
|
-
sections.push("4. Run `pnpm lint` to verify the new config works correctly.");
|
|
3202
|
-
sections.push("");
|
|
3190
|
+
tasks.push("### Migrate .oxlintrc.json to oxlint.config.ts");
|
|
3191
|
+
tasks.push("");
|
|
3192
|
+
tasks.push("Compare `.oxlintrc.json` rules against `@bensandee/config/oxlint/recommended`. Port any custom rules/overrides into `oxlint.config.ts`, then delete `.oxlintrc.json`.");
|
|
3193
|
+
tasks.push("");
|
|
3203
3194
|
} else if (oxlintWasSkipped && detected.hasOxlintConfig) {
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
sections.push(" ```");
|
|
3216
|
-
sections.push("3. If it uses a different pattern, update it to extend the recommended config while preserving any project-specific customizations.");
|
|
3217
|
-
sections.push("4. Run `pnpm lint` to verify the config works correctly.");
|
|
3218
|
-
sections.push("");
|
|
3195
|
+
tasks.push("### Verify oxlint.config.ts extends recommended rules");
|
|
3196
|
+
tasks.push("");
|
|
3197
|
+
tasks.push("Ensure `oxlint.config.ts` imports and extends `@bensandee/config/oxlint/recommended`:");
|
|
3198
|
+
tasks.push("");
|
|
3199
|
+
tasks.push("```ts");
|
|
3200
|
+
tasks.push("import recommended from \"@bensandee/config/oxlint/recommended\";");
|
|
3201
|
+
tasks.push("import { defineConfig } from \"oxlint\";");
|
|
3202
|
+
tasks.push("");
|
|
3203
|
+
tasks.push("export default defineConfig({ extends: [recommended] });");
|
|
3204
|
+
tasks.push("```");
|
|
3205
|
+
tasks.push("");
|
|
3219
3206
|
}
|
|
3220
3207
|
if (config.structure === "monorepo" && !detected.hasPnpmWorkspace) {
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3208
|
+
tasks.push("### Migrate to monorepo structure");
|
|
3209
|
+
tasks.push("");
|
|
3210
|
+
tasks.push("This project was converted to a monorepo. Complete the migration:");
|
|
3211
|
+
tasks.push("");
|
|
3212
|
+
tasks.push("1. Move existing source into `packages/<name>/`");
|
|
3213
|
+
tasks.push("2. Split root `package.json` into workspace manifest + package-level `package.json`");
|
|
3214
|
+
tasks.push("3. Move `tsconfig.json` into the package, add project references to root");
|
|
3215
|
+
tasks.push("4. Create a package-level `tsdown.config.ts`");
|
|
3216
|
+
tasks.push("5. Update import paths and build scripts");
|
|
3217
|
+
tasks.push("");
|
|
3231
3218
|
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3219
|
+
if (results.some((r) => r.filePath === "test/example.test.ts" && r.action === "created")) {
|
|
3220
|
+
tasks.push("### Generate tests");
|
|
3221
|
+
tasks.push("");
|
|
3222
|
+
tasks.push("A starter test was created at `test/example.test.ts`. Review `src/` and create additional tests covering exported functions and edge cases.");
|
|
3223
|
+
tasks.push("");
|
|
3224
|
+
}
|
|
3225
|
+
if (tasks.length > 0) {
|
|
3226
|
+
sections.push("## Migration tasks");
|
|
3237
3227
|
sections.push("");
|
|
3238
|
-
|
|
3228
|
+
sections.push(...tasks);
|
|
3229
|
+
sections.push("## Ground rules");
|
|
3239
3230
|
sections.push("");
|
|
3240
|
-
|
|
3241
|
-
if (results.some((r) => r.filePath === "test/example.test.ts" && r.action === "created")) {
|
|
3242
|
-
sections.push("### Generate tests");
|
|
3231
|
+
sections.push("Prefer fixing root causes over suppressing issues:");
|
|
3243
3232
|
sections.push("");
|
|
3244
|
-
sections.push("
|
|
3233
|
+
sections.push("- **Lint errors**: fix the code, not disable comments");
|
|
3234
|
+
sections.push("- **Type errors**: add proper types, not `any` or `@ts-expect-error`");
|
|
3235
|
+
sections.push("- **Test failures**: fix the test or bug, don't skip/delete");
|
|
3236
|
+
sections.push("- **Knip findings**: remove unused code, don't add ignores");
|
|
3245
3237
|
sections.push("");
|
|
3246
|
-
sections.push("
|
|
3247
|
-
sections.push("2. Create additional test files following the starter test's patterns (import style, describe/it structure)");
|
|
3248
|
-
sections.push("3. Focus on edge cases and core business logic");
|
|
3249
|
-
sections.push("4. Aim for meaningful coverage of exported functions and key code paths");
|
|
3238
|
+
sections.push("It is OK to add new packages (e.g. `zod`, `@bensandee/common`) to resolve errors. Only suppress an issue with a comment explaining why the fix is not feasible.");
|
|
3250
3239
|
sections.push("");
|
|
3251
3240
|
}
|
|
3252
|
-
sections.push("##
|
|
3241
|
+
sections.push("## Verify");
|
|
3253
3242
|
sections.push("");
|
|
3254
|
-
sections.push("
|
|
3243
|
+
sections.push("Run `pnpm check` and fix any errors. This runs the full pipeline:");
|
|
3244
|
+
sections.push("build → typecheck → lint → test → format → knip");
|
|
3255
3245
|
sections.push("");
|
|
3256
|
-
sections.push("When
|
|
3257
|
-
sections.push("");
|
|
3258
|
-
sections.push("- **Lint errors**: fix the code rather than adding disable comments or rule exceptions");
|
|
3259
|
-
sections.push("- **Test failures**: update the test or fix the underlying bug rather than skipping or deleting the test");
|
|
3260
|
-
sections.push("- **Knip findings**: remove genuinely unused code/exports/dependencies rather than adding ignores to `knip.config.ts`");
|
|
3261
|
-
sections.push("- **Type errors**: add proper types rather than using `any` or `@ts-expect-error`");
|
|
3262
|
-
sections.push("");
|
|
3263
|
-
sections.push("Only suppress an issue if there is a clear, documented reason why the fix is not feasible (e.g. a third-party type mismatch). Leave a comment explaining why.");
|
|
3264
|
-
sections.push("");
|
|
3265
|
-
sections.push("## Verification checklist");
|
|
3266
|
-
sections.push("");
|
|
3267
|
-
sections.push("Run each of these commands and fix any errors before moving on:");
|
|
3268
|
-
sections.push("");
|
|
3269
|
-
sections.push("1. `pnpm install`");
|
|
3270
|
-
const updateCmd = `pnpm update --latest ${getAddedDevDepNames(config).join(" ")}`;
|
|
3271
|
-
sections.push(`2. \`${updateCmd}\` — bump added dependencies to their latest versions`);
|
|
3272
|
-
sections.push("3. `pnpm typecheck` — fix any type errors");
|
|
3273
|
-
sections.push("4. `pnpm build` — fix any build errors");
|
|
3274
|
-
sections.push("5. `pnpm test` — fix any test failures");
|
|
3275
|
-
sections.push("6. `pnpm lint` — fix the code to satisfy lint rules");
|
|
3276
|
-
sections.push("7. `pnpm knip` — remove unused exports, dependencies, and dead code");
|
|
3277
|
-
sections.push("8. `pnpm format` — fix any formatting issues");
|
|
3246
|
+
sections.push("When everything passes, run `/init` to generate a `CLAUDE.md` for this repository.");
|
|
3278
3247
|
sections.push("");
|
|
3279
3248
|
return sections.join("\n");
|
|
3280
3249
|
}
|
|
@@ -3301,11 +3270,11 @@ function contextAsDockerReader(ctx) {
|
|
|
3301
3270
|
function logDetectionSummary(ctx) {
|
|
3302
3271
|
if (ctx.config.publishDocker) {
|
|
3303
3272
|
const dockerPackages = detectDockerPackages(contextAsDockerReader(ctx), ctx.targetDir, ctx.config.name);
|
|
3304
|
-
if (dockerPackages.length > 0) log.info(`Docker images: ${dockerPackages.map((pkg) => pkg.imageName).join(", ")}`);
|
|
3273
|
+
if (dockerPackages.length > 0) log.info(`Docker images to build and publish: ${dockerPackages.map((pkg) => pkg.imageName).join(", ")}`);
|
|
3305
3274
|
}
|
|
3306
3275
|
if (ctx.config.publishNpm) {
|
|
3307
3276
|
const publishable = getPublishablePackages(ctx.targetDir, ctx.config.structure, ctx.packageJson);
|
|
3308
|
-
if (publishable.length > 0) log.info(`npm packages: ${publishable.map((pkg) => pkg.name).join(", ")}`);
|
|
3277
|
+
if (publishable.length > 0) log.info(`npm packages to publish: ${publishable.map((pkg) => pkg.name).join(", ")}`);
|
|
3309
3278
|
}
|
|
3310
3279
|
}
|
|
3311
3280
|
async function runInit(config, options = {}) {
|
|
@@ -3353,22 +3322,19 @@ async function runInit(config, options = {}) {
|
|
|
3353
3322
|
if (created.length > 0) summaryLines.push(`Created: ${created.map((r) => r.filePath).join(", ")}`);
|
|
3354
3323
|
if (updated.length > 0) summaryLines.push(`Updated: ${updated.map((r) => r.filePath).join(", ")}`);
|
|
3355
3324
|
note(summaryLines.join("\n"), "Summary");
|
|
3356
|
-
if (!options.noPrompt) {
|
|
3357
|
-
|
|
3358
|
-
log.info("In Claude Code, run: \"Execute the steps in .tooling-migrate.md\"");
|
|
3359
|
-
}
|
|
3360
|
-
const bensandeeDeps = getAddedDevDepNames(config).filter((name) => name.startsWith("@bensandee/"));
|
|
3325
|
+
if (!options.noPrompt) log.info(`Migration prompt written to ${promptPath}`);
|
|
3326
|
+
const packageJsonChanged = results.some((r) => r.filePath === "package.json" && (r.action === "created" || r.action === "updated"));
|
|
3361
3327
|
const hasLockfile = ctx.exists("pnpm-lock.yaml");
|
|
3362
|
-
if (
|
|
3363
|
-
log.info("
|
|
3328
|
+
if (packageJsonChanged && hasLockfile) {
|
|
3329
|
+
log.info("package.json changed — running pnpm install...");
|
|
3364
3330
|
try {
|
|
3365
|
-
execSync(
|
|
3331
|
+
execSync("pnpm install", {
|
|
3366
3332
|
cwd: config.targetDir,
|
|
3367
3333
|
stdio: "inherit",
|
|
3368
|
-
timeout:
|
|
3334
|
+
timeout: 12e4
|
|
3369
3335
|
});
|
|
3370
3336
|
} catch (_error) {
|
|
3371
|
-
log.warn("
|
|
3337
|
+
log.warn("pnpm install failed — run it manually");
|
|
3372
3338
|
}
|
|
3373
3339
|
}
|
|
3374
3340
|
if (hasChanges && ctx.exists("package.json")) try {
|
|
@@ -3379,10 +3345,10 @@ async function runInit(config, options = {}) {
|
|
|
3379
3345
|
});
|
|
3380
3346
|
} catch (_error) {}
|
|
3381
3347
|
note([
|
|
3382
|
-
"
|
|
3383
|
-
"
|
|
3384
|
-
|
|
3385
|
-
].join("\n"), "Next steps");
|
|
3348
|
+
...packageJsonChanged && !hasLockfile ? ["Run: pnpm install"] : [],
|
|
3349
|
+
"Run: pnpm check",
|
|
3350
|
+
...!options.noPrompt ? ["Run: claude \"Execute the migration steps in .tooling-migrate.md\""] : []
|
|
3351
|
+
].map((step, i) => `${i + 1}. ${step}`).join("\n"), "Next steps");
|
|
3386
3352
|
return results;
|
|
3387
3353
|
}
|
|
3388
3354
|
//#endregion
|
|
@@ -4990,7 +4956,7 @@ const dockerCheckCommand = defineCommand({
|
|
|
4990
4956
|
const main = defineCommand({
|
|
4991
4957
|
meta: {
|
|
4992
4958
|
name: "bst",
|
|
4993
|
-
version: "0.
|
|
4959
|
+
version: "0.37.0",
|
|
4994
4960
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
4995
4961
|
},
|
|
4996
4962
|
subCommands: {
|
|
@@ -5006,7 +4972,7 @@ const main = defineCommand({
|
|
|
5006
4972
|
"docker:check": dockerCheckCommand
|
|
5007
4973
|
}
|
|
5008
4974
|
});
|
|
5009
|
-
console.log(`@bensandee/tooling v0.
|
|
4975
|
+
console.log(`@bensandee/tooling v0.37.0`);
|
|
5010
4976
|
async function run() {
|
|
5011
4977
|
await runMain(main);
|
|
5012
4978
|
process.exit(process.exitCode ?? 0);
|