@bensandee/tooling 0.29.0 → 0.31.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 +2 -2
- package/dist/bin.mjs +59 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ The generated `ci:check` script defaults to `pnpm check --skip 'docker:*'` since
|
|
|
62
62
|
|
|
63
63
|
| Command | Description |
|
|
64
64
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
-
| `tooling release:changesets` | Changesets version/publish for Forgejo CI. Flag: `--dry-run`. Env: `FORGEJO_SERVER_URL`, `FORGEJO_REPOSITORY`, `
|
|
65
|
+
| `tooling release:changesets` | Changesets version/publish for Forgejo CI. Flag: `--dry-run`. Env: `FORGEJO_SERVER_URL`, `FORGEJO_REPOSITORY`, `RELEASE_TOKEN`. |
|
|
66
66
|
| `tooling release:simple` | Streamlined release using commit-and-tag-version. |
|
|
67
67
|
| `tooling release:trigger` | Trigger a release workflow. |
|
|
68
68
|
| `tooling forgejo:create-release` | Create a Forgejo release from a tag. |
|
|
@@ -77,7 +77,7 @@ The generated `ci:check` script defaults to `pnpm check --skip 'docker:*'` since
|
|
|
77
77
|
|
|
78
78
|
Docker packages are discovered automatically. Any package with a `Dockerfile` or `docker/Dockerfile` is a Docker package. Image names are derived as `{root-package-name}-{package-name}`, build context defaults to `.` (project root). For single-package repos, `Dockerfile` or `docker/Dockerfile` at the project root is checked.
|
|
79
79
|
|
|
80
|
-
When Docker packages are present, `repo:sync` generates a
|
|
80
|
+
When Docker packages are present, `repo:sync` generates a publish workflow (`.forgejo/workflows/publish.yml` or `.github/workflows/publish.yml`) triggered via `workflow_dispatch` for manual runs. For the `simple` release strategy, docker publishing is also added as a step in the release workflow so it runs automatically after each release.
|
|
81
81
|
|
|
82
82
|
#### Overrides
|
|
83
83
|
|
package/dist/bin.mjs
CHANGED
|
@@ -1172,9 +1172,7 @@ function computeNodeVersionYaml(ctx) {
|
|
|
1172
1172
|
function deployWorkflow(ci, nodeVersionYaml) {
|
|
1173
1173
|
return `${workflowSchemaComment(ci)}name: Publish
|
|
1174
1174
|
on:
|
|
1175
|
-
|
|
1176
|
-
tags:
|
|
1177
|
-
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
1175
|
+
workflow_dispatch:
|
|
1178
1176
|
|
|
1179
1177
|
jobs:
|
|
1180
1178
|
publish:
|
|
@@ -1579,7 +1577,7 @@ function getAddedDevDepNames(config) {
|
|
|
1579
1577
|
const deps = { ...ROOT_DEV_DEPS };
|
|
1580
1578
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
1581
1579
|
deps["@bensandee/config"] = "0.9.1";
|
|
1582
|
-
deps["@bensandee/tooling"] = "0.
|
|
1580
|
+
deps["@bensandee/tooling"] = "0.31.0";
|
|
1583
1581
|
if (config.formatter === "oxfmt") deps["oxfmt"] = {
|
|
1584
1582
|
"@changesets/cli": "2.30.0",
|
|
1585
1583
|
"@release-it/bumper": "7.0.5",
|
|
@@ -1634,7 +1632,7 @@ async function generatePackageJson(ctx) {
|
|
|
1634
1632
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1635
1633
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1636
1634
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
|
|
1637
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1635
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.31.0";
|
|
1638
1636
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1639
1637
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1640
1638
|
"@changesets/cli": "2.30.0",
|
|
@@ -2779,7 +2777,7 @@ function releaseItWorkflow(ci, nodeVersionYaml, publishesNpm) {
|
|
|
2779
2777
|
permissions:
|
|
2780
2778
|
contents: write
|
|
2781
2779
|
` : "";
|
|
2782
|
-
const tokenEnv = isGitHub ? `GITHUB_TOKEN: \${{ github.token }}` : `
|
|
2780
|
+
const tokenEnv = isGitHub ? `GITHUB_TOKEN: \${{ github.token }}` : `RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}`;
|
|
2783
2781
|
const npmEnv = publishesNpm ? `\n NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}` : "";
|
|
2784
2782
|
return `${workflowSchemaComment(ci)}name: Release
|
|
2785
2783
|
on:
|
|
@@ -2795,7 +2793,18 @@ ${commonSteps(nodeVersionYaml, publishesNpm)}
|
|
|
2795
2793
|
${tokenEnv}${npmEnv}
|
|
2796
2794
|
`;
|
|
2797
2795
|
}
|
|
2798
|
-
function
|
|
2796
|
+
function dockerPublishStep() {
|
|
2797
|
+
return `
|
|
2798
|
+
- name: Publish Docker images
|
|
2799
|
+
if: success()
|
|
2800
|
+
env:
|
|
2801
|
+
DOCKER_REGISTRY_HOST: ${actionsExpr("vars.DOCKER_REGISTRY_HOST")}
|
|
2802
|
+
DOCKER_REGISTRY_NAMESPACE: ${actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")}
|
|
2803
|
+
DOCKER_USERNAME: ${actionsExpr("secrets.DOCKER_USERNAME")}
|
|
2804
|
+
DOCKER_PASSWORD: ${actionsExpr("secrets.DOCKER_PASSWORD")}
|
|
2805
|
+
run: pnpm exec bst docker:publish`;
|
|
2806
|
+
}
|
|
2807
|
+
function commitAndTagVersionWorkflow(ci, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
2799
2808
|
const isGitHub = ci === "github";
|
|
2800
2809
|
const permissions = isGitHub ? `
|
|
2801
2810
|
permissions:
|
|
@@ -2815,8 +2824,9 @@ permissions:
|
|
|
2815
2824
|
env:
|
|
2816
2825
|
FORGEJO_SERVER_URL: \${{ github.server_url }}
|
|
2817
2826
|
FORGEJO_REPOSITORY: \${{ github.repository }}
|
|
2818
|
-
|
|
2827
|
+
RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}
|
|
2819
2828
|
run: pnpm exec bst release:simple`;
|
|
2829
|
+
const dockerStep = hasDocker ? dockerPublishStep() : "";
|
|
2820
2830
|
return `${workflowSchemaComment(ci)}name: Release
|
|
2821
2831
|
on:
|
|
2822
2832
|
workflow_dispatch:
|
|
@@ -2825,7 +2835,7 @@ jobs:
|
|
|
2825
2835
|
release:
|
|
2826
2836
|
runs-on: ubuntu-latest
|
|
2827
2837
|
steps:
|
|
2828
|
-
${commonSteps(nodeVersionYaml, publishesNpm)}${gitConfigStep}${releaseStep}
|
|
2838
|
+
${commonSteps(nodeVersionYaml, publishesNpm)}${gitConfigStep}${releaseStep}${dockerStep}
|
|
2829
2839
|
`;
|
|
2830
2840
|
}
|
|
2831
2841
|
/** Build the required release step for the check job (changesets). */
|
|
@@ -2853,14 +2863,14 @@ function changesetsReleaseStep(ci, publishesNpm) {
|
|
|
2853
2863
|
env: {
|
|
2854
2864
|
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2855
2865
|
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2856
|
-
|
|
2866
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
2857
2867
|
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
2858
2868
|
},
|
|
2859
2869
|
run: "pnpm exec bst release:changesets"
|
|
2860
2870
|
}
|
|
2861
2871
|
};
|
|
2862
2872
|
}
|
|
2863
|
-
function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
|
|
2873
|
+
function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
2864
2874
|
const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
|
|
2865
2875
|
const steps = [
|
|
2866
2876
|
{
|
|
@@ -2902,6 +2912,13 @@ function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
|
|
|
2902
2912
|
match: { run: "release:simple" },
|
|
2903
2913
|
step: { run: "pnpm exec bst release:simple" }
|
|
2904
2914
|
});
|
|
2915
|
+
if (hasDocker) steps.push({
|
|
2916
|
+
match: { run: "docker:publish" },
|
|
2917
|
+
step: {
|
|
2918
|
+
name: "Publish Docker images",
|
|
2919
|
+
run: "pnpm exec bst docker:publish"
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2905
2922
|
break;
|
|
2906
2923
|
case "changesets":
|
|
2907
2924
|
steps.push({
|
|
@@ -2912,10 +2929,10 @@ function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
|
|
|
2912
2929
|
}
|
|
2913
2930
|
return steps;
|
|
2914
2931
|
}
|
|
2915
|
-
function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm) {
|
|
2932
|
+
function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
2916
2933
|
switch (strategy) {
|
|
2917
2934
|
case "release-it": return releaseItWorkflow(ci, nodeVersionYaml, publishesNpm);
|
|
2918
|
-
case "simple": return commitAndTagVersionWorkflow(ci, nodeVersionYaml, publishesNpm);
|
|
2935
|
+
case "simple": return commitAndTagVersionWorkflow(ci, nodeVersionYaml, publishesNpm, hasDocker);
|
|
2919
2936
|
default: return null;
|
|
2920
2937
|
}
|
|
2921
2938
|
}
|
|
@@ -2965,7 +2982,8 @@ async function generateReleaseCi(ctx) {
|
|
|
2965
2982
|
const isGitHub = ctx.config.ci === "github";
|
|
2966
2983
|
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
2967
2984
|
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2968
|
-
const
|
|
2985
|
+
const hasDocker = hasDockerPackages(ctx);
|
|
2986
|
+
const content = buildWorkflow(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker);
|
|
2969
2987
|
if (!content) return {
|
|
2970
2988
|
filePath,
|
|
2971
2989
|
action: "skipped",
|
|
@@ -2990,7 +3008,7 @@ async function generateReleaseCi(ctx) {
|
|
|
2990
3008
|
description: "Release workflow already up to date"
|
|
2991
3009
|
};
|
|
2992
3010
|
}
|
|
2993
|
-
const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps(ctx.config.releaseStrategy, nodeVersionYaml, publishesNpm));
|
|
3011
|
+
const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps(ctx.config.releaseStrategy, nodeVersionYaml, publishesNpm, hasDocker));
|
|
2994
3012
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2995
3013
|
if (!merged.changed) {
|
|
2996
3014
|
if (withComment !== raw) {
|
|
@@ -3319,7 +3337,7 @@ function generateMigratePrompt(results, config, detected) {
|
|
|
3319
3337
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3320
3338
|
sections.push("# Migration Prompt");
|
|
3321
3339
|
sections.push("");
|
|
3322
|
-
sections.push(`_Generated by \`@bensandee/tooling@0.
|
|
3340
|
+
sections.push(`_Generated by \`@bensandee/tooling@0.31.0 repo:sync\` on ${timestamp}_`);
|
|
3323
3341
|
sections.push("");
|
|
3324
3342
|
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.");
|
|
3325
3343
|
sections.push("");
|
|
@@ -4181,16 +4199,17 @@ const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
|
|
|
4181
4199
|
* Resolve the hosting platform and connection details.
|
|
4182
4200
|
*
|
|
4183
4201
|
* Priority:
|
|
4184
|
-
* 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY,
|
|
4202
|
+
* 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, RELEASE_TOKEN)
|
|
4185
4203
|
* 2. `repository` field in package.json (server URL and owner/repo parsed from the URL)
|
|
4186
4204
|
*
|
|
4187
|
-
* For Forgejo,
|
|
4205
|
+
* For Forgejo, a token is always required. Accepts RELEASE_TOKEN (preferred) or
|
|
4206
|
+
* FORGEJO_TOKEN (legacy, but conflicts with Forgejo Actions' internal auth).
|
|
4188
4207
|
* If the repository URL hostname is `github.com`, returns `{ type: "github" }`.
|
|
4189
4208
|
*/
|
|
4190
4209
|
function resolveConnection(cwd) {
|
|
4191
4210
|
const serverUrl = process.env["FORGEJO_SERVER_URL"];
|
|
4192
4211
|
const repository = process.env["FORGEJO_REPOSITORY"];
|
|
4193
|
-
const token = process.env["FORGEJO_TOKEN"];
|
|
4212
|
+
const token = process.env["RELEASE_TOKEN"] ?? process.env["FORGEJO_TOKEN"];
|
|
4194
4213
|
if (serverUrl && repository && token) return {
|
|
4195
4214
|
type: "forgejo",
|
|
4196
4215
|
conn: {
|
|
@@ -4203,13 +4222,13 @@ function resolveConnection(cwd) {
|
|
|
4203
4222
|
if (parsed === null) {
|
|
4204
4223
|
if (serverUrl) {
|
|
4205
4224
|
if (!repository) throw new FatalError("FORGEJO_REPOSITORY environment variable is required");
|
|
4206
|
-
if (!token) throw new FatalError("
|
|
4225
|
+
if (!token) throw new FatalError("RELEASE_TOKEN environment variable is required");
|
|
4207
4226
|
}
|
|
4208
4227
|
return { type: "github" };
|
|
4209
4228
|
}
|
|
4210
4229
|
if (parsed.hostname === "github.com") return { type: "github" };
|
|
4211
4230
|
const resolvedToken = token;
|
|
4212
|
-
if (!resolvedToken) throw new FatalError("
|
|
4231
|
+
if (!resolvedToken) throw new FatalError("RELEASE_TOKEN environment variable is required (server URL and repository were resolved from package.json)");
|
|
4213
4232
|
return {
|
|
4214
4233
|
type: "forgejo",
|
|
4215
4234
|
conn: {
|
|
@@ -4247,6 +4266,18 @@ function parseGitUrl(urlStr) {
|
|
|
4247
4266
|
return null;
|
|
4248
4267
|
}
|
|
4249
4268
|
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Configure the git remote origin to use a Forgejo token for push auth.
|
|
4271
|
+
*
|
|
4272
|
+
* This is necessary because `actions/checkout` configures git with the automatic
|
|
4273
|
+
* GITHUB_TOKEN, which may not have push permissions. Using RELEASE_TOKEN ensures
|
|
4274
|
+
* pushes use the explicitly provided token.
|
|
4275
|
+
*/
|
|
4276
|
+
function configureGitAuth(executor, conn, cwd) {
|
|
4277
|
+
const host = conn.serverUrl.replace(/^https?:\/\//, "");
|
|
4278
|
+
const authUrl = `https://x-access-token:${conn.token}@${host}/${conn.repository}`;
|
|
4279
|
+
executor.exec(`git remote set-url origin ${authUrl}`, { cwd });
|
|
4280
|
+
}
|
|
4250
4281
|
//#endregion
|
|
4251
4282
|
//#region src/commands/release-changesets.ts
|
|
4252
4283
|
const releaseForgejoCommand = defineCommand({
|
|
@@ -4297,6 +4328,7 @@ async function runRelease(config, executor) {
|
|
|
4297
4328
|
}
|
|
4298
4329
|
executor.exec("git config user.name \"forgejo-actions[bot]\"", { cwd: config.cwd });
|
|
4299
4330
|
executor.exec("git config user.email \"forgejo-actions[bot]@noreply.localhost\"", { cwd: config.cwd });
|
|
4331
|
+
configureGitAuth(executor, config, config.cwd);
|
|
4300
4332
|
const changesetFiles = executor.listChangesetFiles(config.cwd);
|
|
4301
4333
|
debug(config, `Changeset files found: ${changesetFiles.length > 0 ? changesetFiles.join(", ") : "(none)"}`);
|
|
4302
4334
|
if (changesetFiles.length > 0) {
|
|
@@ -4470,6 +4502,10 @@ async function runSimpleRelease(executor, config) {
|
|
|
4470
4502
|
}
|
|
4471
4503
|
let pushed = false;
|
|
4472
4504
|
if (!config.noPush) {
|
|
4505
|
+
if (config.platform?.type === "forgejo") {
|
|
4506
|
+
configureGitAuth(executor, config.platform.conn, config.cwd);
|
|
4507
|
+
debug(config, "Configured git remote with RELEASE_TOKEN auth");
|
|
4508
|
+
}
|
|
4473
4509
|
const branch = executor.exec("git rev-parse --abbrev-ref HEAD", { cwd: config.cwd }).stdout.trim() || "main";
|
|
4474
4510
|
debug(config, `Pushing to origin/${branch}`);
|
|
4475
4511
|
const pushResult = executor.exec(`git push --follow-tags origin ${branch}`, { cwd: config.cwd });
|
|
@@ -5109,7 +5145,7 @@ const dockerCheckCommand = defineCommand({
|
|
|
5109
5145
|
const main = defineCommand({
|
|
5110
5146
|
meta: {
|
|
5111
5147
|
name: "bst",
|
|
5112
|
-
version: "0.
|
|
5148
|
+
version: "0.31.0",
|
|
5113
5149
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
5114
5150
|
},
|
|
5115
5151
|
subCommands: {
|
|
@@ -5125,7 +5161,7 @@ const main = defineCommand({
|
|
|
5125
5161
|
"docker:check": dockerCheckCommand
|
|
5126
5162
|
}
|
|
5127
5163
|
});
|
|
5128
|
-
console.log(`@bensandee/tooling v0.
|
|
5164
|
+
console.log(`@bensandee/tooling v0.31.0`);
|
|
5129
5165
|
async function run() {
|
|
5130
5166
|
await runMain(main);
|
|
5131
5167
|
process.exit(process.exitCode ?? 0);
|