@bensandee/tooling 0.27.0 → 0.28.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 +116 -50
- package/dist/index.d.mts +2 -0
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -113,6 +113,7 @@ const LEGACY_PATTERNS = {
|
|
|
113
113
|
/** Detect existing project state in the target directory. */
|
|
114
114
|
function detectProject(targetDir) {
|
|
115
115
|
const exists = (rel) => existsSync(path.join(targetDir, rel));
|
|
116
|
+
const rootPkg = readPackageJson(targetDir);
|
|
116
117
|
return {
|
|
117
118
|
hasPackageJson: exists("package.json"),
|
|
118
119
|
hasTsconfig: exists("tsconfig.json"),
|
|
@@ -127,7 +128,8 @@ function detectProject(targetDir) {
|
|
|
127
128
|
hasReleaseItConfig: exists(".release-it.json") || exists(".release-it.yaml") || exists(".release-it.toml"),
|
|
128
129
|
hasSimpleReleaseConfig: exists(".versionrc") || exists(".versionrc.json") || exists(".versionrc.js"),
|
|
129
130
|
hasChangesetsConfig: exists(".changeset/config.json"),
|
|
130
|
-
hasRepositoryField: !!
|
|
131
|
+
hasRepositoryField: !!rootPkg?.repository,
|
|
132
|
+
hasCommitAndTagVersion: !!rootPkg?.devDependencies?.["commit-and-tag-version"],
|
|
131
133
|
legacyConfigs: detectLegacyConfigs(targetDir)
|
|
132
134
|
};
|
|
133
135
|
}
|
|
@@ -664,6 +666,10 @@ function ensureSchemaComment(content, ci) {
|
|
|
664
666
|
if (content.includes("yaml-language-server")) return content;
|
|
665
667
|
return FORGEJO_SCHEMA_COMMENT + content;
|
|
666
668
|
}
|
|
669
|
+
/** Migrate content from old tooling binary name to new. */
|
|
670
|
+
function migrateToolingBinary(content) {
|
|
671
|
+
return content.replaceAll("pnpm exec tooling ", "pnpm exec bst ");
|
|
672
|
+
}
|
|
667
673
|
/** Check if a YAML file has an opt-out comment in the first 10 lines. */
|
|
668
674
|
function isToolingIgnored(content) {
|
|
669
675
|
return content.split("\n", 10).some((line) => line.includes(IGNORE_PATTERN));
|
|
@@ -797,7 +803,7 @@ function ensureWorkflowConcurrency(existing, concurrency) {
|
|
|
797
803
|
}
|
|
798
804
|
}
|
|
799
805
|
//#endregion
|
|
800
|
-
//#region src/generators/
|
|
806
|
+
//#region src/generators/publish-ci.ts
|
|
801
807
|
/** Build a GitHub Actions expression like `${{ expr }}` without triggering no-template-curly-in-string. */
|
|
802
808
|
function actionsExpr$2(expr) {
|
|
803
809
|
return `\${{ ${expr} }}`;
|
|
@@ -806,14 +812,14 @@ function hasEnginesNode$2(ctx) {
|
|
|
806
812
|
return typeof ctx.packageJson?.["engines"]?.["node"] === "string";
|
|
807
813
|
}
|
|
808
814
|
function deployWorkflow(ci, nodeVersionYaml) {
|
|
809
|
-
return `${workflowSchemaComment(ci)}name:
|
|
815
|
+
return `${workflowSchemaComment(ci)}name: Publish
|
|
810
816
|
on:
|
|
811
817
|
push:
|
|
812
818
|
tags:
|
|
813
819
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
814
820
|
|
|
815
821
|
jobs:
|
|
816
|
-
|
|
822
|
+
publish:
|
|
817
823
|
runs-on: ubuntu-latest
|
|
818
824
|
steps:
|
|
819
825
|
- uses: actions/checkout@v4
|
|
@@ -898,48 +904,59 @@ async function generateDeployCi(ctx) {
|
|
|
898
904
|
const nodeVersionYaml = hasEnginesNode$2(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
|
|
899
905
|
const content = deployWorkflow(ctx.config.ci, nodeVersionYaml);
|
|
900
906
|
if (ctx.exists(workflowPath)) {
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
907
|
+
const raw = ctx.read(workflowPath);
|
|
908
|
+
if (raw) {
|
|
909
|
+
const existing = migrateToolingBinary(raw);
|
|
910
|
+
if (existing === content || ensureSchemaComment(existing, ctx.config.ci) === content) {
|
|
911
|
+
if (existing !== raw) {
|
|
912
|
+
ctx.write(workflowPath, ensureSchemaComment(existing, ctx.config.ci));
|
|
913
|
+
return {
|
|
914
|
+
filePath: workflowPath,
|
|
915
|
+
action: "updated",
|
|
916
|
+
description: "Migrated tooling binary name in publish workflow"
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
filePath: workflowPath,
|
|
921
|
+
action: "skipped",
|
|
922
|
+
description: "Publish workflow already up to date"
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
const merged = mergeWorkflowSteps(existing, "publish", requiredDeploySteps());
|
|
909
926
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
910
927
|
if (!merged.changed) {
|
|
911
|
-
if (withComment !==
|
|
928
|
+
if (withComment !== raw) {
|
|
912
929
|
ctx.write(workflowPath, withComment);
|
|
913
930
|
return {
|
|
914
931
|
filePath: workflowPath,
|
|
915
932
|
action: "updated",
|
|
916
|
-
description: "Added schema comment to
|
|
933
|
+
description: existing !== raw ? "Migrated tooling binary name in publish workflow" : "Added schema comment to publish workflow"
|
|
917
934
|
};
|
|
918
935
|
}
|
|
919
936
|
return {
|
|
920
937
|
filePath: workflowPath,
|
|
921
938
|
action: "skipped",
|
|
922
|
-
description: "Existing
|
|
939
|
+
description: "Existing publish workflow preserved"
|
|
923
940
|
};
|
|
924
941
|
}
|
|
925
942
|
ctx.write(workflowPath, withComment);
|
|
926
943
|
return {
|
|
927
944
|
filePath: workflowPath,
|
|
928
945
|
action: "updated",
|
|
929
|
-
description: "Added missing steps to
|
|
946
|
+
description: "Added missing steps to publish workflow"
|
|
930
947
|
};
|
|
931
948
|
}
|
|
932
949
|
return {
|
|
933
950
|
filePath: workflowPath,
|
|
934
951
|
action: "skipped",
|
|
935
|
-
description: "
|
|
952
|
+
description: "Publish workflow already up to date"
|
|
936
953
|
};
|
|
937
954
|
}
|
|
938
955
|
ctx.write(workflowPath, content);
|
|
939
956
|
return {
|
|
940
957
|
filePath: workflowPath,
|
|
941
958
|
action: "created",
|
|
942
|
-
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions
|
|
959
|
+
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions publish workflow`
|
|
943
960
|
};
|
|
944
961
|
}
|
|
945
962
|
//#endregion
|
|
@@ -969,13 +986,21 @@ const STANDARD_SCRIPTS_MONOREPO = {
|
|
|
969
986
|
};
|
|
970
987
|
/** Scripts that tooling owns — map from script name to keyword that must appear in the value. */
|
|
971
988
|
const MANAGED_SCRIPTS = {
|
|
972
|
-
check: "checks:run",
|
|
989
|
+
check: "bst checks:run",
|
|
973
990
|
"ci:check": "pnpm check",
|
|
974
|
-
"tooling:check": "repo:sync --check",
|
|
975
|
-
"tooling:sync": "repo:sync",
|
|
976
|
-
"
|
|
977
|
-
"docker:
|
|
991
|
+
"tooling:check": "bst repo:sync --check",
|
|
992
|
+
"tooling:sync": "bst repo:sync",
|
|
993
|
+
"trigger-release": "bst release:trigger",
|
|
994
|
+
"docker:build": "bst docker:build",
|
|
995
|
+
"docker:check": "bst docker:check"
|
|
978
996
|
};
|
|
997
|
+
/** Check if an existing script value satisfies a managed script requirement.
|
|
998
|
+
* Accepts both `bst <cmd>` and `bin.mjs <cmd>` (used in the tooling repo itself). */
|
|
999
|
+
function matchesManagedScript(scriptValue, expectedFragment) {
|
|
1000
|
+
if (scriptValue.includes(expectedFragment)) return true;
|
|
1001
|
+
const binMjsFragment = expectedFragment.replace(/^bst /, "bin.mjs ");
|
|
1002
|
+
return scriptValue.includes(binMjsFragment);
|
|
1003
|
+
}
|
|
979
1004
|
/** Deprecated scripts to remove during migration. */
|
|
980
1005
|
const DEPRECATED_SCRIPTS = ["tooling:init", "tooling:update"];
|
|
981
1006
|
/** DevDeps that belong in every project (single repo) or per-package (monorepo). */
|
|
@@ -1031,7 +1056,7 @@ function getAddedDevDepNames(config) {
|
|
|
1031
1056
|
const deps = { ...ROOT_DEV_DEPS };
|
|
1032
1057
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
1033
1058
|
deps["@bensandee/config"] = "0.9.1";
|
|
1034
|
-
deps["@bensandee/tooling"] = "0.
|
|
1059
|
+
deps["@bensandee/tooling"] = "0.28.0";
|
|
1035
1060
|
if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
|
|
1036
1061
|
if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
|
|
1037
1062
|
addReleaseDeps(deps, config);
|
|
@@ -1056,7 +1081,7 @@ async function generatePackageJson(ctx) {
|
|
|
1056
1081
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1057
1082
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1058
1083
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
|
|
1059
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1084
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.28.0";
|
|
1060
1085
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1061
1086
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
|
|
1062
1087
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
|
|
@@ -1074,10 +1099,14 @@ async function generatePackageJson(ctx) {
|
|
|
1074
1099
|
changes.push("set type: \"module\"");
|
|
1075
1100
|
}
|
|
1076
1101
|
const existingScripts = pkg.scripts ?? {};
|
|
1102
|
+
for (const [key, value] of Object.entries(existingScripts)) if (typeof value === "string" && value.includes("pnpm exec tooling ")) {
|
|
1103
|
+
existingScripts[key] = migrateToolingBinary(value);
|
|
1104
|
+
changes.push(`migrated script: ${key}`);
|
|
1105
|
+
}
|
|
1077
1106
|
for (const [key, value] of Object.entries(allScripts)) if (!(key in existingScripts)) {
|
|
1078
1107
|
existingScripts[key] = value;
|
|
1079
1108
|
changes.push(`added script: ${key}`);
|
|
1080
|
-
} else if (key in MANAGED_SCRIPTS && !existingScripts[key]
|
|
1109
|
+
} else if (key in MANAGED_SCRIPTS && !matchesManagedScript(existingScripts[key] ?? "", MANAGED_SCRIPTS[key] ?? "")) {
|
|
1081
1110
|
existingScripts[key] = value;
|
|
1082
1111
|
changes.push(`updated script: ${key}`);
|
|
1083
1112
|
}
|
|
@@ -2135,7 +2164,7 @@ function actionsExpr(expr) {
|
|
|
2135
2164
|
function hasEnginesNode(ctx) {
|
|
2136
2165
|
return typeof ctx.packageJson?.["engines"]?.["node"] === "string";
|
|
2137
2166
|
}
|
|
2138
|
-
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
2167
|
+
function commonSteps(nodeVersionYaml, publishesNpm, { build = true } = {}) {
|
|
2139
2168
|
return ` - uses: actions/checkout@v4
|
|
2140
2169
|
with:
|
|
2141
2170
|
fetch-depth: 0
|
|
@@ -2144,8 +2173,7 @@ function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
|
2144
2173
|
with:
|
|
2145
2174
|
${nodeVersionYaml}
|
|
2146
2175
|
cache: pnpm${publishesNpm ? `\n registry-url: "https://registry.npmjs.org"` : ""}
|
|
2147
|
-
- run: pnpm install --frozen-lockfile
|
|
2148
|
-
- run: pnpm build`;
|
|
2176
|
+
- run: pnpm install --frozen-lockfile${build ? `\n - run: pnpm build` : ""}`;
|
|
2149
2177
|
}
|
|
2150
2178
|
function releaseItWorkflow(ci, nodeVersionYaml, publishesNpm) {
|
|
2151
2179
|
const isGitHub = ci === "github";
|
|
@@ -2199,7 +2227,7 @@ jobs:
|
|
|
2199
2227
|
release:
|
|
2200
2228
|
runs-on: ubuntu-latest
|
|
2201
2229
|
steps:
|
|
2202
|
-
${commonSteps(nodeVersionYaml, publishesNpm)}${gitConfigStep}${releaseStep}
|
|
2230
|
+
${commonSteps(nodeVersionYaml, publishesNpm, { build: false })}${gitConfigStep}${releaseStep}
|
|
2203
2231
|
`;
|
|
2204
2232
|
}
|
|
2205
2233
|
/** Build the required release step for the check job (changesets). */
|
|
@@ -2263,10 +2291,10 @@ function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
|
|
|
2263
2291
|
match: { run: "pnpm install" },
|
|
2264
2292
|
step: { run: "pnpm install --frozen-lockfile" }
|
|
2265
2293
|
},
|
|
2266
|
-
{
|
|
2294
|
+
...strategy !== "simple" ? [{
|
|
2267
2295
|
match: { run: "build" },
|
|
2268
2296
|
step: { run: "pnpm build" }
|
|
2269
|
-
}
|
|
2297
|
+
}] : []
|
|
2270
2298
|
];
|
|
2271
2299
|
switch (strategy) {
|
|
2272
2300
|
case "release-it":
|
|
@@ -2299,18 +2327,30 @@ function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm) {
|
|
|
2299
2327
|
}
|
|
2300
2328
|
function generateChangesetsReleaseCi(ctx, publishesNpm) {
|
|
2301
2329
|
const ciPath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
2302
|
-
const
|
|
2303
|
-
if (!
|
|
2330
|
+
const raw = ctx.read(ciPath);
|
|
2331
|
+
if (!raw) return {
|
|
2304
2332
|
filePath: ciPath,
|
|
2305
2333
|
action: "skipped",
|
|
2306
2334
|
description: "CI workflow not found — run check generator first"
|
|
2307
2335
|
};
|
|
2336
|
+
const existing = migrateToolingBinary(raw);
|
|
2308
2337
|
const merged = mergeWorkflowSteps(existing, "check", [changesetsReleaseStep(ctx.config.ci, publishesNpm)]);
|
|
2309
|
-
if (!merged.changed)
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2338
|
+
if (!merged.changed) {
|
|
2339
|
+
if (existing !== raw) {
|
|
2340
|
+
const withComment = ensureSchemaComment(existing, ctx.config.ci);
|
|
2341
|
+
ctx.write(ciPath, withComment);
|
|
2342
|
+
return {
|
|
2343
|
+
filePath: ciPath,
|
|
2344
|
+
action: "updated",
|
|
2345
|
+
description: "Migrated tooling binary name in CI workflow"
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
return {
|
|
2349
|
+
filePath: ciPath,
|
|
2350
|
+
action: "skipped",
|
|
2351
|
+
description: "Release step in CI workflow already up to date"
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2314
2354
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2315
2355
|
ctx.write(ciPath, withComment);
|
|
2316
2356
|
return {
|
|
@@ -2338,22 +2378,33 @@ async function generateReleaseCi(ctx) {
|
|
|
2338
2378
|
description: "Release CI workflow not applicable"
|
|
2339
2379
|
};
|
|
2340
2380
|
if (ctx.exists(workflowPath)) {
|
|
2341
|
-
const
|
|
2342
|
-
if (
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2381
|
+
const raw = ctx.read(workflowPath);
|
|
2382
|
+
if (raw) {
|
|
2383
|
+
const existing = migrateToolingBinary(raw);
|
|
2384
|
+
if (existing === content || ensureSchemaComment(existing, ctx.config.ci) === content) {
|
|
2385
|
+
if (existing !== raw) {
|
|
2386
|
+
ctx.write(workflowPath, ensureSchemaComment(existing, ctx.config.ci));
|
|
2387
|
+
return {
|
|
2388
|
+
filePath: workflowPath,
|
|
2389
|
+
action: "updated",
|
|
2390
|
+
description: "Migrated tooling binary name in release workflow"
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
return {
|
|
2394
|
+
filePath: workflowPath,
|
|
2395
|
+
action: "skipped",
|
|
2396
|
+
description: "Release workflow already up to date"
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2348
2399
|
const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps(ctx.config.releaseStrategy, nodeVersionYaml, publishesNpm));
|
|
2349
2400
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2350
2401
|
if (!merged.changed) {
|
|
2351
|
-
if (withComment !==
|
|
2402
|
+
if (withComment !== raw) {
|
|
2352
2403
|
ctx.write(workflowPath, withComment);
|
|
2353
2404
|
return {
|
|
2354
2405
|
filePath: workflowPath,
|
|
2355
2406
|
action: "updated",
|
|
2356
|
-
description: "Added schema comment to release workflow"
|
|
2407
|
+
description: existing !== raw ? "Migrated tooling binary name in release workflow" : "Added schema comment to release workflow"
|
|
2357
2408
|
};
|
|
2358
2409
|
}
|
|
2359
2410
|
return {
|
|
@@ -2953,8 +3004,11 @@ function runDockerPublish(executor, config) {
|
|
|
2953
3004
|
*/
|
|
2954
3005
|
function generateMigratePrompt(results, config, detected) {
|
|
2955
3006
|
const sections = [];
|
|
3007
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2956
3008
|
sections.push("# Migration Prompt");
|
|
2957
3009
|
sections.push("");
|
|
3010
|
+
sections.push(`_Generated by \`@bensandee/tooling@0.28.0 repo:sync\` on ${timestamp}_`);
|
|
3011
|
+
sections.push("");
|
|
2958
3012
|
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.");
|
|
2959
3013
|
sections.push("");
|
|
2960
3014
|
sections.push("> **Tip:** Before starting, run `/init` in Claude Code to generate a `CLAUDE.md` that gives the AI a complete picture of your repository's structure, conventions, and build commands.");
|
|
@@ -2987,6 +3041,18 @@ function generateMigratePrompt(results, config, detected) {
|
|
|
2987
3041
|
}
|
|
2988
3042
|
sections.push("## Migration tasks");
|
|
2989
3043
|
sections.push("");
|
|
3044
|
+
if (config.releaseStrategy === "simple" && !detected.hasCommitAndTagVersion) {
|
|
3045
|
+
sections.push("### Add commit-and-tag-version to devDependencies");
|
|
3046
|
+
sections.push("");
|
|
3047
|
+
sections.push("The `simple` release strategy requires `commit-and-tag-version` as a root devDependency so that `pnpm exec commit-and-tag-version` resolves correctly.");
|
|
3048
|
+
sections.push("");
|
|
3049
|
+
sections.push("Run:");
|
|
3050
|
+
sections.push("");
|
|
3051
|
+
sections.push("```sh");
|
|
3052
|
+
sections.push("pnpm add -D -w commit-and-tag-version");
|
|
3053
|
+
sections.push("```");
|
|
3054
|
+
sections.push("");
|
|
3055
|
+
}
|
|
2990
3056
|
if (config.releaseStrategy !== "none" && !detected.hasRepositoryField) {
|
|
2991
3057
|
sections.push("### Add repository field to package.json");
|
|
2992
3058
|
sections.push("");
|
|
@@ -4754,7 +4820,7 @@ const dockerCheckCommand = defineCommand({
|
|
|
4754
4820
|
const main = defineCommand({
|
|
4755
4821
|
meta: {
|
|
4756
4822
|
name: "bst",
|
|
4757
|
-
version: "0.
|
|
4823
|
+
version: "0.28.0",
|
|
4758
4824
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
4759
4825
|
},
|
|
4760
4826
|
subCommands: {
|
|
@@ -4770,7 +4836,7 @@ const main = defineCommand({
|
|
|
4770
4836
|
"docker:check": dockerCheckCommand
|
|
4771
4837
|
}
|
|
4772
4838
|
});
|
|
4773
|
-
console.log(`@bensandee/tooling v0.
|
|
4839
|
+
console.log(`@bensandee/tooling v0.28.0`);
|
|
4774
4840
|
async function run() {
|
|
4775
4841
|
await runMain(main);
|
|
4776
4842
|
process.exit(process.exitCode ?? 0);
|
package/dist/index.d.mts
CHANGED
|
@@ -100,6 +100,8 @@ interface DetectedProjectState {
|
|
|
100
100
|
hasChangesetsConfig: boolean;
|
|
101
101
|
/** Whether package.json has a repository field (needed for release workflows) */
|
|
102
102
|
hasRepositoryField: boolean;
|
|
103
|
+
/** Whether commit-and-tag-version is in root devDependencies (required for simple release strategy) */
|
|
104
|
+
hasCommitAndTagVersion: boolean;
|
|
103
105
|
/** Legacy tooling configs found */
|
|
104
106
|
legacyConfigs: LegacyConfig[];
|
|
105
107
|
}
|