@bensandee/tooling 0.32.0 → 0.33.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 CHANGED
@@ -63,11 +63,37 @@ The generated `ci:check` script defaults to `pnpm check --skip 'docker:*'` since
63
63
  | Command | Description |
64
64
  | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
65
65
  | `tooling release:changesets` | Changesets version/publish for Forgejo CI. Flag: `--dry-run`. Env: `FORGEJO_SERVER_URL`, `FORGEJO_REPOSITORY`, `RELEASE_TOKEN`. |
66
- | `tooling release:simple` | Streamlined release using commit-and-tag-version. |
66
+ | `tooling release:simple` | Streamlined release using commit-and-tag-version. Flags: `--release-as`, `--first-release`, `--prerelease`. |
67
67
  | `tooling release:trigger` | Trigger a release workflow. |
68
68
  | `tooling forgejo:create-release` | Create a Forgejo release from a tag. |
69
69
  | `tooling changesets:merge` | Merge a changesets version PR. |
70
70
 
71
+ #### `release:simple`
72
+
73
+ Uses `commit-and-tag-version` under the hood. Version bumps are auto-detected from [Conventional Commits](https://www.conventionalcommits.org/):
74
+
75
+ | Commit prefix | Bump | Example |
76
+ | ----------------------- | ----- | ------------------------------------ |
77
+ | `fix:` | patch | `fix: handle null response` |
78
+ | `feat:` | minor | `feat: add retry logic` |
79
+ | `feat!:` / `fix!:` etc | major | `feat!: drop v1 API` |
80
+ | `BREAKING CHANGE:` body | major | Any type with breaking change footer |
81
+
82
+ Override auto-detection with CLI flags:
83
+
84
+ ```bash
85
+ # Force a major bump
86
+ tooling release:simple --release-as major
87
+
88
+ # Force a specific version
89
+ tooling release:simple --release-as 2.0.0
90
+
91
+ # Create a prerelease
92
+ tooling release:simple --release-as major --prerelease beta # → 2.0.0-beta.0
93
+ ```
94
+
95
+ The generated release workflow exposes these as optional `workflow_dispatch` inputs (`bump` and `prerelease`), so bumps can also be controlled from the CI UI.
96
+
71
97
  ### Docker
72
98
 
73
99
  | Command | Description |
@@ -133,14 +159,16 @@ Each package is tagged independently using its own version, so packages in a mon
133
159
 
134
160
  **Flags:** `--dry-run` (build and tag only, skip login/push/logout)
135
161
 
136
- **Required environment variables:**
162
+ **Required CI variables:**
163
+
164
+ | Variable | Type | Description |
165
+ | --------------------------- | -------- | --------------------------------------------------------------------- |
166
+ | `DOCKER_REGISTRY_HOST` | variable | Registry hostname (e.g. `code.orangebikelabs.com`) |
167
+ | `DOCKER_REGISTRY_NAMESPACE` | variable | Full namespace for tagging (e.g. `code.orangebikelabs.com/bensandee`) |
168
+ | `DOCKER_USERNAME` | secret | Registry username |
169
+ | `DOCKER_PASSWORD` | secret | Registry password |
137
170
 
138
- | Variable | Description |
139
- | --------------------------- | --------------------------------------------------------------------- |
140
- | `DOCKER_REGISTRY_HOST` | Registry hostname (e.g. `code.orangebikelabs.com`) |
141
- | `DOCKER_REGISTRY_NAMESPACE` | Full namespace for tagging (e.g. `code.orangebikelabs.com/bensandee`) |
142
- | `DOCKER_USERNAME` | Registry username |
143
- | `DOCKER_PASSWORD` | Registry password |
171
+ **Forgejo setup:** On Forgejo, `DOCKER_USERNAME` is your Forgejo account username, and `DOCKER_PASSWORD` can reuse the same token as `RELEASE_TOKEN`. The token needs write permissions on the org, package, and repository scopes. These permissions should be set for the **user** if the package is under a user namespace (e.g. `bensandee`), or the **organization** if it's under an org namespace (e.g. `orangebikelabs`).
144
172
 
145
173
  ## Config file
146
174
 
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { l as createRealExecutor$1, t as runDockerCheck, u as isExecSyncError } from "./check-DMDdHanG.mjs";
2
+ import { l as createRealExecutor$1, t as runDockerCheck, u as isExecSyncError } from "./check-B2AAPCBO.mjs";
3
3
  import { defineCommand, runMain } from "citty";
4
4
  import * as clack from "@clack/prompts";
5
5
  import { isCancel, select } from "@clack/prompts";
@@ -1590,7 +1590,7 @@ function getAddedDevDepNames(config) {
1590
1590
  const deps = { ...ROOT_DEV_DEPS };
1591
1591
  if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
1592
1592
  deps["@bensandee/config"] = "0.9.1";
1593
- deps["@bensandee/tooling"] = "0.32.0";
1593
+ deps["@bensandee/tooling"] = "0.33.0";
1594
1594
  if (config.formatter === "oxfmt") deps["oxfmt"] = {
1595
1595
  "@changesets/cli": "2.30.0",
1596
1596
  "@release-it/bumper": "7.0.5",
@@ -1645,7 +1645,7 @@ async function generatePackageJson(ctx) {
1645
1645
  const devDeps = { ...ROOT_DEV_DEPS };
1646
1646
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1647
1647
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
1648
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.32.0";
1648
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.33.0";
1649
1649
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
1650
1650
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1651
1651
  "@changesets/cli": "2.30.0",
@@ -2804,6 +2804,41 @@ function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
2804
2804
  }
2805
2805
  }];
2806
2806
  }
2807
+ /** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
2808
+ function simpleWorkflowDispatchTrigger() {
2809
+ return { workflow_dispatch: { inputs: {
2810
+ bump: {
2811
+ description: "Version bump type (default: conventional-commits auto-detect)",
2812
+ required: false,
2813
+ type: "choice",
2814
+ default: "auto",
2815
+ options: [
2816
+ "auto",
2817
+ "major",
2818
+ "minor",
2819
+ "patch",
2820
+ "first-release"
2821
+ ]
2822
+ },
2823
+ prerelease: {
2824
+ description: "Create a prerelease with the given tag (e.g., beta, alpha)",
2825
+ required: false,
2826
+ type: "string"
2827
+ }
2828
+ } } };
2829
+ }
2830
+ /** Build the release:simple run command with conditional flags from workflow inputs. */
2831
+ function simpleReleaseCommand() {
2832
+ return [
2833
+ "FLAGS=",
2834
+ `case "${actionsExpr("inputs.bump")}" in`,
2835
+ " major|minor|patch) FLAGS=\"$FLAGS --release-as " + actionsExpr("inputs.bump") + "\" ;;",
2836
+ " first-release) FLAGS=\"$FLAGS --first-release\" ;;",
2837
+ "esac",
2838
+ `if [ -n "${actionsExpr("inputs.prerelease")}" ]; then FLAGS="$FLAGS --prerelease ${actionsExpr("inputs.prerelease")}"; fi`,
2839
+ "pnpm exec bst release:simple $FLAGS"
2840
+ ].join("\n");
2841
+ }
2807
2842
  function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker) {
2808
2843
  const releaseStep = {
2809
2844
  match: { run: "release:simple" },
@@ -2814,7 +2849,7 @@ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker) {
2814
2849
  FORGEJO_REPOSITORY: actionsExpr("github.repository"),
2815
2850
  RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
2816
2851
  },
2817
- run: "pnpm exec bst release:simple"
2852
+ run: simpleReleaseCommand()
2818
2853
  }
2819
2854
  };
2820
2855
  const dockerStep = {
@@ -2929,10 +2964,11 @@ async function generateReleaseCi(ctx) {
2929
2964
  action: "skipped",
2930
2965
  description: "Release CI workflow not applicable"
2931
2966
  };
2967
+ const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
2932
2968
  const content = buildWorkflowYaml({
2933
2969
  ci: ctx.config.ci,
2934
2970
  name: "Release",
2935
- on: { workflow_dispatch: null },
2971
+ on,
2936
2972
  ...isGitHub && { permissions: { contents: "write" } },
2937
2973
  jobName: "release",
2938
2974
  steps
@@ -3285,7 +3321,7 @@ function generateMigratePrompt(results, config, detected) {
3285
3321
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3286
3322
  sections.push("# Migration Prompt");
3287
3323
  sections.push("");
3288
- sections.push(`_Generated by \`@bensandee/tooling@0.32.0 repo:sync\` on ${timestamp}_`);
3324
+ sections.push(`_Generated by \`@bensandee/tooling@0.33.0 repo:sync\` on ${timestamp}_`);
3289
3325
  sections.push("");
3290
3326
  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.");
3291
3327
  sections.push("");
@@ -4293,33 +4329,53 @@ const releaseTriggerCommand = defineCommand({
4293
4329
  name: "release:trigger",
4294
4330
  description: "Trigger the release CI workflow"
4295
4331
  },
4296
- args: { ref: {
4297
- type: "string",
4298
- description: "Git ref to trigger on (default: main)",
4299
- required: false
4300
- } },
4332
+ args: {
4333
+ ref: {
4334
+ type: "string",
4335
+ description: "Git ref to trigger on (default: main)",
4336
+ required: false
4337
+ },
4338
+ bump: {
4339
+ type: "string",
4340
+ description: "Version bump type: auto, major, minor, patch, or first-release",
4341
+ required: false
4342
+ },
4343
+ prerelease: {
4344
+ type: "string",
4345
+ description: "Create a prerelease with the given tag (e.g., beta, alpha)",
4346
+ required: false
4347
+ }
4348
+ },
4301
4349
  async run({ args }) {
4302
4350
  const ref = args.ref ?? "main";
4351
+ const inputs = {};
4352
+ if (args.bump) inputs["bump"] = args.bump;
4353
+ if (args.prerelease) inputs["prerelease"] = args.prerelease;
4303
4354
  const resolved = resolveConnection(process.cwd());
4304
- if (resolved.type === "forgejo") await triggerForgejo(resolved.conn, ref);
4305
- else triggerGitHub(ref);
4355
+ if (resolved.type === "forgejo") await triggerForgejo(resolved.conn, ref, inputs);
4356
+ else triggerGitHub(ref, inputs);
4306
4357
  }
4307
4358
  });
4308
- async function triggerForgejo(conn, ref) {
4359
+ async function triggerForgejo(conn, ref, inputs) {
4309
4360
  const url = `${conn.serverUrl}/api/v1/repos/${conn.repository}/actions/workflows/release.yml/dispatches`;
4361
+ const body = { ref };
4362
+ if (Object.keys(inputs).length > 0) body["inputs"] = inputs;
4310
4363
  const res = await fetch(url, {
4311
4364
  method: "POST",
4312
4365
  headers: {
4313
4366
  Authorization: `token ${conn.token}`,
4314
4367
  "Content-Type": "application/json"
4315
4368
  },
4316
- body: JSON.stringify({ ref })
4369
+ body: JSON.stringify(body)
4317
4370
  });
4318
4371
  if (!res.ok) throw new FatalError(`Failed to trigger Forgejo workflow: ${res.status} ${res.statusText}`);
4319
4372
  log$2.info(`Triggered release workflow on Forgejo (ref: ${ref})`);
4320
4373
  }
4321
- function triggerGitHub(ref) {
4322
- const result = createRealExecutor().exec(`gh workflow run release.yml --ref ${ref}`, { cwd: process.cwd() });
4374
+ function triggerGitHub(ref, inputs) {
4375
+ const executor = createRealExecutor();
4376
+ const inputFlags = Object.entries(inputs).map(([k, v]) => `-f ${k}=${v}`).join(" ");
4377
+ const cmd = `gh workflow run release.yml --ref ${ref}${inputFlags ? ` ${inputFlags}` : ""}`;
4378
+ const result = executor.exec(cmd, { cwd: process.cwd() });
4323
4379
  if (result.exitCode !== 0) throw new FatalError(`Failed to trigger GitHub workflow: ${result.stderr || result.stdout || "unknown error"}`);
4324
4380
  log$2.info(`Triggered release workflow on GitHub (ref: ${ref})`);
4325
4381
  }
@@ -5103,7 +5159,7 @@ const dockerCheckCommand = defineCommand({
5103
5159
  const main = defineCommand({
5104
5160
  meta: {
5105
5161
  name: "bst",
5106
- version: "0.32.0",
5162
+ version: "0.33.0",
5107
5163
  description: "Bootstrap and maintain standardized TypeScript project tooling"
5108
5164
  },
5109
5165
  subCommands: {
@@ -5119,7 +5175,7 @@ const main = defineCommand({
5119
5175
  "docker:check": dockerCheckCommand
5120
5176
  }
5121
5177
  });
5122
- console.log(`@bensandee/tooling v0.32.0`);
5178
+ console.log(`@bensandee/tooling v0.33.0`);
5123
5179
  async function run() {
5124
5180
  await runMain(main);
5125
5181
  process.exit(process.exitCode ?? 0);
@@ -145,14 +145,19 @@ async function runDockerCheck(executor, config) {
145
145
  const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
146
146
  const { compose } = config;
147
147
  const cleanup = () => composeDown(executor, compose);
148
- const disposeInt = executor.onSignal("SIGINT", () => {
149
- cleanup();
150
- process.exit(1);
151
- });
152
- const disposeTerm = executor.onSignal("SIGTERM", () => {
148
+ let cleaningUp = false;
149
+ const gracefulShutdown = () => {
150
+ if (cleaningUp) {
151
+ executor.log("Cleanup in progress, please wait...");
152
+ return;
153
+ }
154
+ cleaningUp = true;
155
+ executor.log("Interrupted — shutting down compose stack...");
153
156
  cleanup();
154
157
  process.exit(1);
155
- });
158
+ };
159
+ const disposeInt = executor.onSignal("SIGINT", gracefulShutdown);
160
+ const disposeTerm = executor.onSignal("SIGTERM", gracefulShutdown);
156
161
  try {
157
162
  if (config.buildCommand) {
158
163
  executor.log("Building images...");
@@ -162,6 +167,7 @@ async function runDockerCheck(executor, config) {
162
167
  composeUp(executor, compose);
163
168
  executor.log(`Waiting for stack to be healthy (max ${timeoutMs / 1e3}s)...`);
164
169
  const startTime = executor.now();
170
+ let lastStatusLogTime = startTime;
165
171
  const healthStatus = new Map(config.healthChecks.map((c) => [c.name, false]));
166
172
  while (executor.now() - startTime < timeoutMs) {
167
173
  const containers = composePs(executor, compose);
@@ -195,10 +201,12 @@ async function runDockerCheck(executor, config) {
195
201
  elapsedMs: executor.now() - startTime
196
202
  };
197
203
  }
198
- const elapsed = Math.floor((executor.now() - startTime) / 1e3);
199
- if (elapsed > 0 && elapsed % 5 === 0) {
200
- const statuses = [...healthStatus.entries()].map(([name, ok]) => `${name}=${ok ? "OK" : "Pending"}`).join(", ");
201
- executor.log(`Waiting... (${elapsed}s elapsed). ${statuses}`);
204
+ const now = executor.now();
205
+ if (now - lastStatusLogTime >= 5e3) {
206
+ lastStatusLogTime = now;
207
+ const elapsed = Math.floor((now - startTime) / 1e3);
208
+ const parts = [compose.services.map((s) => `${s}=${getContainerHealth(containers, s)}`).join(", "), [...healthStatus.entries()].map(([name, ok]) => `${name}=${ok ? "OK" : "Pending"}`).join(", ")].filter(Boolean).join(" | ");
209
+ executor.log(`Waiting... (${elapsed}s elapsed). ${parts}`);
202
210
  }
203
211
  await executor.sleep(pollIntervalMs);
204
212
  }
@@ -1,2 +1,2 @@
1
- import { a as composeDown, c as composeUp, i as composeCommand, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-DMDdHanG.mjs";
1
+ import { a as composeDown, c as composeUp, i as composeCommand, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-B2AAPCBO.mjs";
2
2
  export { checkHttpHealth, composeCommand, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "bst": "./dist/bin.mjs"