@bensandee/tooling 0.31.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";
@@ -970,10 +970,6 @@ function runDockerPublish(executor, config) {
970
970
  //#region src/utils/yaml-merge.ts
971
971
  const IGNORE_PATTERN = "@bensandee/tooling:ignore";
972
972
  const FORGEJO_SCHEMA_COMMENT = "# yaml-language-server: $schema=../../.vscode/forgejo-workflow.schema.json\n";
973
- /** Returns a yaml-language-server schema comment for Forgejo workflows, empty string otherwise. */
974
- function workflowSchemaComment(ci) {
975
- return ci === "forgejo" ? FORGEJO_SCHEMA_COMMENT : "";
976
- }
977
973
  /** Prepend the Forgejo schema comment if it's not already present. No-op for GitHub. */
978
974
  function ensureSchemaComment(content, ci) {
979
975
  if (ci !== "forgejo") return content;
@@ -1028,6 +1024,29 @@ function mergeLefthookCommands(existing, requiredCommands) {
1028
1024
  };
1029
1025
  }
1030
1026
  }
1027
+ /** Extract only the mergeable steps (those with a match) as RequiredSteps. */
1028
+ function toRequiredSteps(steps) {
1029
+ return steps.filter((s) => s.match !== void 0).map((s) => ({
1030
+ match: s.match,
1031
+ step: s.step
1032
+ }));
1033
+ }
1034
+ /** Build a complete workflow YAML string from structured options. Single source of truth for both new files and merge steps. */
1035
+ function buildWorkflowYaml(options) {
1036
+ const doc = { name: options.name };
1037
+ if (options.enableEmailNotifications) doc["enable-email-notifications"] = true;
1038
+ doc["on"] = options.on;
1039
+ if (options.permissions) doc["permissions"] = options.permissions;
1040
+ if (options.concurrency) doc["concurrency"] = options.concurrency;
1041
+ doc["jobs"] = { [options.jobName]: {
1042
+ "runs-on": options.runsOn ?? "ubuntu-latest",
1043
+ steps: options.steps.map((s) => s.step)
1044
+ } };
1045
+ return ensureSchemaComment(stringify(doc, {
1046
+ lineWidth: 0,
1047
+ nullStr: ""
1048
+ }), options.ci);
1049
+ }
1031
1050
  /**
1032
1051
  * Ensure required steps exist in a workflow job's steps array.
1033
1052
  * Only adds missing steps at the end — never modifies existing ones.
@@ -1169,43 +1188,22 @@ function computeNodeVersionYaml(ctx) {
1169
1188
  }
1170
1189
  //#endregion
1171
1190
  //#region src/generators/publish-ci.ts
1172
- function deployWorkflow(ci, nodeVersionYaml) {
1173
- return `${workflowSchemaComment(ci)}name: Publish
1174
- on:
1175
- workflow_dispatch:
1176
-
1177
- jobs:
1178
- publish:
1179
- runs-on: ubuntu-latest
1180
- steps:
1181
- - uses: actions/checkout@v4
1182
- - uses: pnpm/action-setup@v4
1183
- - uses: actions/setup-node@v4
1184
- with:
1185
- ${nodeVersionYaml}
1186
- - run: pnpm install --frozen-lockfile
1187
- - name: Publish Docker images
1188
- env:
1189
- DOCKER_REGISTRY_HOST: ${actionsExpr("vars.DOCKER_REGISTRY_HOST")}
1190
- DOCKER_REGISTRY_NAMESPACE: ${actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")}
1191
- DOCKER_USERNAME: ${actionsExpr("secrets.DOCKER_USERNAME")}
1192
- DOCKER_PASSWORD: ${actionsExpr("secrets.DOCKER_PASSWORD")}
1193
- run: pnpm exec bst docker:publish
1194
- `;
1195
- }
1196
- function requiredDeploySteps() {
1191
+ function publishSteps(nodeVersionYaml) {
1197
1192
  return [
1198
1193
  {
1199
1194
  match: { uses: "actions/checkout" },
1200
- step: { uses: "actions/checkout@v4" }
1195
+ step: { uses: "actions/checkout@v6" }
1201
1196
  },
1202
1197
  {
1203
1198
  match: { uses: "pnpm/action-setup" },
1204
- step: { uses: "pnpm/action-setup@v4" }
1199
+ step: { uses: "pnpm/action-setup@v5" }
1205
1200
  },
1206
1201
  {
1207
1202
  match: { uses: "actions/setup-node" },
1208
- step: { uses: "actions/setup-node@v4" }
1203
+ step: {
1204
+ uses: "actions/setup-node@v6",
1205
+ with: nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" }
1206
+ }
1209
1207
  },
1210
1208
  {
1211
1209
  match: { run: "pnpm install" },
@@ -1213,7 +1211,16 @@ function requiredDeploySteps() {
1213
1211
  },
1214
1212
  {
1215
1213
  match: { run: "docker:publish" },
1216
- step: { run: "pnpm exec bst docker:publish" }
1214
+ step: {
1215
+ name: "Publish Docker images",
1216
+ env: {
1217
+ DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
1218
+ DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE"),
1219
+ DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
1220
+ DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD")
1221
+ },
1222
+ run: "pnpm exec bst docker:publish"
1223
+ }
1217
1224
  }
1218
1225
  ];
1219
1226
  }
@@ -1255,8 +1262,14 @@ async function generateDeployCi(ctx) {
1255
1262
  };
1256
1263
  const isGitHub = ctx.config.ci === "github";
1257
1264
  const workflowPath = isGitHub ? ".github/workflows/publish.yml" : ".forgejo/workflows/publish.yml";
1258
- const nodeVersionYaml = computeNodeVersionYaml(ctx);
1259
- const content = deployWorkflow(ctx.config.ci, nodeVersionYaml);
1265
+ const steps = publishSteps(computeNodeVersionYaml(ctx));
1266
+ const content = buildWorkflowYaml({
1267
+ ci: ctx.config.ci,
1268
+ name: "Publish",
1269
+ on: { workflow_dispatch: null },
1270
+ jobName: "publish",
1271
+ steps
1272
+ });
1260
1273
  if (ctx.exists(workflowPath)) {
1261
1274
  const raw = ctx.read(workflowPath);
1262
1275
  if (raw) {
@@ -1276,7 +1289,7 @@ async function generateDeployCi(ctx) {
1276
1289
  description: "Publish workflow already up to date"
1277
1290
  };
1278
1291
  }
1279
- const merged = mergeWorkflowSteps(existing, "publish", requiredDeploySteps());
1292
+ const merged = mergeWorkflowSteps(existing, "publish", toRequiredSteps(steps));
1280
1293
  const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
1281
1294
  if (!merged.changed) {
1282
1295
  if (withComment !== raw) {
@@ -1362,6 +1375,7 @@ const PER_PACKAGE_DEV_DEPS = {
1362
1375
  "@types/node": {
1363
1376
  "@changesets/cli": "2.30.0",
1364
1377
  "@release-it/bumper": "7.0.5",
1378
+ "@types/node": "24.12.0",
1365
1379
  "commit-and-tag-version": "12.7.0",
1366
1380
  "knip": "5.87.0",
1367
1381
  "lefthook": "2.1.4",
@@ -1369,7 +1383,6 @@ const PER_PACKAGE_DEV_DEPS = {
1369
1383
  "oxlint": "1.56.0",
1370
1384
  "prettier": "3.8.1",
1371
1385
  "release-it": "19.2.4",
1372
- "@types/node": "24.12.0",
1373
1386
  "tsdown": "0.21.4",
1374
1387
  "typescript": "5.9.3",
1375
1388
  "vitest": "4.1.0",
@@ -1378,6 +1391,7 @@ const PER_PACKAGE_DEV_DEPS = {
1378
1391
  tsdown: {
1379
1392
  "@changesets/cli": "2.30.0",
1380
1393
  "@release-it/bumper": "7.0.5",
1394
+ "@types/node": "24.12.0",
1381
1395
  "commit-and-tag-version": "12.7.0",
1382
1396
  "knip": "5.87.0",
1383
1397
  "lefthook": "2.1.4",
@@ -1385,7 +1399,6 @@ const PER_PACKAGE_DEV_DEPS = {
1385
1399
  "oxlint": "1.56.0",
1386
1400
  "prettier": "3.8.1",
1387
1401
  "release-it": "19.2.4",
1388
- "@types/node": "24.12.0",
1389
1402
  "tsdown": "0.21.4",
1390
1403
  "typescript": "5.9.3",
1391
1404
  "vitest": "4.1.0",
@@ -1394,6 +1407,7 @@ const PER_PACKAGE_DEV_DEPS = {
1394
1407
  typescript: {
1395
1408
  "@changesets/cli": "2.30.0",
1396
1409
  "@release-it/bumper": "7.0.5",
1410
+ "@types/node": "24.12.0",
1397
1411
  "commit-and-tag-version": "12.7.0",
1398
1412
  "knip": "5.87.0",
1399
1413
  "lefthook": "2.1.4",
@@ -1401,7 +1415,6 @@ const PER_PACKAGE_DEV_DEPS = {
1401
1415
  "oxlint": "1.56.0",
1402
1416
  "prettier": "3.8.1",
1403
1417
  "release-it": "19.2.4",
1404
- "@types/node": "24.12.0",
1405
1418
  "tsdown": "0.21.4",
1406
1419
  "typescript": "5.9.3",
1407
1420
  "vitest": "4.1.0",
@@ -1410,6 +1423,7 @@ const PER_PACKAGE_DEV_DEPS = {
1410
1423
  vitest: {
1411
1424
  "@changesets/cli": "2.30.0",
1412
1425
  "@release-it/bumper": "7.0.5",
1426
+ "@types/node": "24.12.0",
1413
1427
  "commit-and-tag-version": "12.7.0",
1414
1428
  "knip": "5.87.0",
1415
1429
  "lefthook": "2.1.4",
@@ -1417,7 +1431,6 @@ const PER_PACKAGE_DEV_DEPS = {
1417
1431
  "oxlint": "1.56.0",
1418
1432
  "prettier": "3.8.1",
1419
1433
  "release-it": "19.2.4",
1420
- "@types/node": "24.12.0",
1421
1434
  "tsdown": "0.21.4",
1422
1435
  "typescript": "5.9.3",
1423
1436
  "vitest": "4.1.0",
@@ -1429,6 +1442,7 @@ const ROOT_DEV_DEPS = {
1429
1442
  knip: {
1430
1443
  "@changesets/cli": "2.30.0",
1431
1444
  "@release-it/bumper": "7.0.5",
1445
+ "@types/node": "24.12.0",
1432
1446
  "commit-and-tag-version": "12.7.0",
1433
1447
  "knip": "5.87.0",
1434
1448
  "lefthook": "2.1.4",
@@ -1436,7 +1450,6 @@ const ROOT_DEV_DEPS = {
1436
1450
  "oxlint": "1.56.0",
1437
1451
  "prettier": "3.8.1",
1438
1452
  "release-it": "19.2.4",
1439
- "@types/node": "24.12.0",
1440
1453
  "tsdown": "0.21.4",
1441
1454
  "typescript": "5.9.3",
1442
1455
  "vitest": "4.1.0",
@@ -1445,6 +1458,7 @@ const ROOT_DEV_DEPS = {
1445
1458
  lefthook: {
1446
1459
  "@changesets/cli": "2.30.0",
1447
1460
  "@release-it/bumper": "7.0.5",
1461
+ "@types/node": "24.12.0",
1448
1462
  "commit-and-tag-version": "12.7.0",
1449
1463
  "knip": "5.87.0",
1450
1464
  "lefthook": "2.1.4",
@@ -1452,7 +1466,6 @@ const ROOT_DEV_DEPS = {
1452
1466
  "oxlint": "1.56.0",
1453
1467
  "prettier": "3.8.1",
1454
1468
  "release-it": "19.2.4",
1455
- "@types/node": "24.12.0",
1456
1469
  "tsdown": "0.21.4",
1457
1470
  "typescript": "5.9.3",
1458
1471
  "vitest": "4.1.0",
@@ -1461,6 +1474,7 @@ const ROOT_DEV_DEPS = {
1461
1474
  oxlint: {
1462
1475
  "@changesets/cli": "2.30.0",
1463
1476
  "@release-it/bumper": "7.0.5",
1477
+ "@types/node": "24.12.0",
1464
1478
  "commit-and-tag-version": "12.7.0",
1465
1479
  "knip": "5.87.0",
1466
1480
  "lefthook": "2.1.4",
@@ -1468,7 +1482,6 @@ const ROOT_DEV_DEPS = {
1468
1482
  "oxlint": "1.56.0",
1469
1483
  "prettier": "3.8.1",
1470
1484
  "release-it": "19.2.4",
1471
- "@types/node": "24.12.0",
1472
1485
  "tsdown": "0.21.4",
1473
1486
  "typescript": "5.9.3",
1474
1487
  "vitest": "4.1.0",
@@ -1504,6 +1517,7 @@ function addReleaseDeps(deps, config) {
1504
1517
  deps["release-it"] = {
1505
1518
  "@changesets/cli": "2.30.0",
1506
1519
  "@release-it/bumper": "7.0.5",
1520
+ "@types/node": "24.12.0",
1507
1521
  "commit-and-tag-version": "12.7.0",
1508
1522
  "knip": "5.87.0",
1509
1523
  "lefthook": "2.1.4",
@@ -1511,7 +1525,6 @@ function addReleaseDeps(deps, config) {
1511
1525
  "oxlint": "1.56.0",
1512
1526
  "prettier": "3.8.1",
1513
1527
  "release-it": "19.2.4",
1514
- "@types/node": "24.12.0",
1515
1528
  "tsdown": "0.21.4",
1516
1529
  "typescript": "5.9.3",
1517
1530
  "vitest": "4.1.0",
@@ -1520,6 +1533,7 @@ function addReleaseDeps(deps, config) {
1520
1533
  if (config.structure === "monorepo") deps["@release-it/bumper"] = {
1521
1534
  "@changesets/cli": "2.30.0",
1522
1535
  "@release-it/bumper": "7.0.5",
1536
+ "@types/node": "24.12.0",
1523
1537
  "commit-and-tag-version": "12.7.0",
1524
1538
  "knip": "5.87.0",
1525
1539
  "lefthook": "2.1.4",
@@ -1527,7 +1541,6 @@ function addReleaseDeps(deps, config) {
1527
1541
  "oxlint": "1.56.0",
1528
1542
  "prettier": "3.8.1",
1529
1543
  "release-it": "19.2.4",
1530
- "@types/node": "24.12.0",
1531
1544
  "tsdown": "0.21.4",
1532
1545
  "typescript": "5.9.3",
1533
1546
  "vitest": "4.1.0",
@@ -1538,6 +1551,7 @@ function addReleaseDeps(deps, config) {
1538
1551
  deps["commit-and-tag-version"] = {
1539
1552
  "@changesets/cli": "2.30.0",
1540
1553
  "@release-it/bumper": "7.0.5",
1554
+ "@types/node": "24.12.0",
1541
1555
  "commit-and-tag-version": "12.7.0",
1542
1556
  "knip": "5.87.0",
1543
1557
  "lefthook": "2.1.4",
@@ -1545,7 +1559,6 @@ function addReleaseDeps(deps, config) {
1545
1559
  "oxlint": "1.56.0",
1546
1560
  "prettier": "3.8.1",
1547
1561
  "release-it": "19.2.4",
1548
- "@types/node": "24.12.0",
1549
1562
  "tsdown": "0.21.4",
1550
1563
  "typescript": "5.9.3",
1551
1564
  "vitest": "4.1.0",
@@ -1556,6 +1569,7 @@ function addReleaseDeps(deps, config) {
1556
1569
  deps["@changesets/cli"] = {
1557
1570
  "@changesets/cli": "2.30.0",
1558
1571
  "@release-it/bumper": "7.0.5",
1572
+ "@types/node": "24.12.0",
1559
1573
  "commit-and-tag-version": "12.7.0",
1560
1574
  "knip": "5.87.0",
1561
1575
  "lefthook": "2.1.4",
@@ -1563,7 +1577,6 @@ function addReleaseDeps(deps, config) {
1563
1577
  "oxlint": "1.56.0",
1564
1578
  "prettier": "3.8.1",
1565
1579
  "release-it": "19.2.4",
1566
- "@types/node": "24.12.0",
1567
1580
  "tsdown": "0.21.4",
1568
1581
  "typescript": "5.9.3",
1569
1582
  "vitest": "4.1.0",
@@ -1577,10 +1590,11 @@ function getAddedDevDepNames(config) {
1577
1590
  const deps = { ...ROOT_DEV_DEPS };
1578
1591
  if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
1579
1592
  deps["@bensandee/config"] = "0.9.1";
1580
- deps["@bensandee/tooling"] = "0.31.0";
1593
+ deps["@bensandee/tooling"] = "0.33.0";
1581
1594
  if (config.formatter === "oxfmt") deps["oxfmt"] = {
1582
1595
  "@changesets/cli": "2.30.0",
1583
1596
  "@release-it/bumper": "7.0.5",
1597
+ "@types/node": "24.12.0",
1584
1598
  "commit-and-tag-version": "12.7.0",
1585
1599
  "knip": "5.87.0",
1586
1600
  "lefthook": "2.1.4",
@@ -1588,7 +1602,6 @@ function getAddedDevDepNames(config) {
1588
1602
  "oxlint": "1.56.0",
1589
1603
  "prettier": "3.8.1",
1590
1604
  "release-it": "19.2.4",
1591
- "@types/node": "24.12.0",
1592
1605
  "tsdown": "0.21.4",
1593
1606
  "typescript": "5.9.3",
1594
1607
  "vitest": "4.1.0",
@@ -1597,6 +1610,7 @@ function getAddedDevDepNames(config) {
1597
1610
  if (config.formatter === "prettier") deps["prettier"] = {
1598
1611
  "@changesets/cli": "2.30.0",
1599
1612
  "@release-it/bumper": "7.0.5",
1613
+ "@types/node": "24.12.0",
1600
1614
  "commit-and-tag-version": "12.7.0",
1601
1615
  "knip": "5.87.0",
1602
1616
  "lefthook": "2.1.4",
@@ -1604,7 +1618,6 @@ function getAddedDevDepNames(config) {
1604
1618
  "oxlint": "1.56.0",
1605
1619
  "prettier": "3.8.1",
1606
1620
  "release-it": "19.2.4",
1607
- "@types/node": "24.12.0",
1608
1621
  "tsdown": "0.21.4",
1609
1622
  "typescript": "5.9.3",
1610
1623
  "vitest": "4.1.0",
@@ -1632,11 +1645,12 @@ async function generatePackageJson(ctx) {
1632
1645
  const devDeps = { ...ROOT_DEV_DEPS };
1633
1646
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1634
1647
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
1635
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.31.0";
1648
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.33.0";
1636
1649
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
1637
1650
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1638
1651
  "@changesets/cli": "2.30.0",
1639
1652
  "@release-it/bumper": "7.0.5",
1653
+ "@types/node": "24.12.0",
1640
1654
  "commit-and-tag-version": "12.7.0",
1641
1655
  "knip": "5.87.0",
1642
1656
  "lefthook": "2.1.4",
@@ -1644,7 +1658,6 @@ async function generatePackageJson(ctx) {
1644
1658
  "oxlint": "1.56.0",
1645
1659
  "prettier": "3.8.1",
1646
1660
  "release-it": "19.2.4",
1647
- "@types/node": "24.12.0",
1648
1661
  "tsdown": "0.21.4",
1649
1662
  "typescript": "5.9.3",
1650
1663
  "vitest": "4.1.0",
@@ -1653,6 +1666,7 @@ async function generatePackageJson(ctx) {
1653
1666
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
1654
1667
  "@changesets/cli": "2.30.0",
1655
1668
  "@release-it/bumper": "7.0.5",
1669
+ "@types/node": "24.12.0",
1656
1670
  "commit-and-tag-version": "12.7.0",
1657
1671
  "knip": "5.87.0",
1658
1672
  "lefthook": "2.1.4",
@@ -1660,7 +1674,6 @@ async function generatePackageJson(ctx) {
1660
1674
  "oxlint": "1.56.0",
1661
1675
  "prettier": "3.8.1",
1662
1676
  "release-it": "19.2.4",
1663
- "@types/node": "24.12.0",
1664
1677
  "tsdown": "0.21.4",
1665
1678
  "typescript": "5.9.3",
1666
1679
  "vitest": "4.1.0",
@@ -1741,6 +1754,7 @@ async function generatePackageJson(ctx) {
1741
1754
  packageManager: `pnpm@${{
1742
1755
  "@changesets/cli": "2.30.0",
1743
1756
  "@release-it/bumper": "7.0.5",
1757
+ "@types/node": "24.12.0",
1744
1758
  "commit-and-tag-version": "12.7.0",
1745
1759
  "knip": "5.87.0",
1746
1760
  "lefthook": "2.1.4",
@@ -1748,7 +1762,6 @@ async function generatePackageJson(ctx) {
1748
1762
  "oxlint": "1.56.0",
1749
1763
  "prettier": "3.8.1",
1750
1764
  "release-it": "19.2.4",
1751
- "@types/node": "24.12.0",
1752
1765
  "tsdown": "0.21.4",
1753
1766
  "typescript": "5.9.3",
1754
1767
  "vitest": "4.1.0",
@@ -2174,50 +2187,20 @@ const CI_CONCURRENCY = {
2174
2187
  group: `ci-${actionsExpr("github.ref")}`,
2175
2188
  "cancel-in-progress": actionsExpr("github.ref != 'refs/heads/main'")
2176
2189
  };
2177
- function ciWorkflow(nodeVersionYaml, isForgejo, isChangesets) {
2178
- const emailNotifications = isForgejo ? "\nenable-email-notifications: true\n" : "";
2179
- const concurrencyBlock = isChangesets ? `
2180
- concurrency:
2181
- group: ci-${actionsExpr("github.ref")}
2182
- cancel-in-progress: ${actionsExpr("github.ref != 'refs/heads/main'")}
2183
- ` : "";
2184
- return `${workflowSchemaComment(isForgejo ? "forgejo" : "github")}name: CI
2185
- ${emailNotifications}on:
2186
- push:
2187
- branches: [main]
2188
- tags-ignore:
2189
- - "**"
2190
- pull_request:
2191
- ${concurrencyBlock}
2192
- jobs:
2193
- check:
2194
- runs-on: ubuntu-latest
2195
- steps:
2196
- - uses: actions/checkout@v4
2197
- - uses: pnpm/action-setup@v4
2198
- - uses: actions/setup-node@v4
2199
- with:
2200
- ${nodeVersionYaml}
2201
- cache: pnpm
2202
- - run: pnpm install --frozen-lockfile
2203
- - name: Run all checks
2204
- run: pnpm ci:check
2205
- `;
2206
- }
2207
- function requiredCheckSteps(nodeVersionYaml) {
2190
+ function checkSteps(nodeVersionYaml) {
2208
2191
  return [
2209
2192
  {
2210
2193
  match: { uses: "actions/checkout" },
2211
- step: { uses: "actions/checkout@v4" }
2194
+ step: { uses: "actions/checkout@v6" }
2212
2195
  },
2213
2196
  {
2214
2197
  match: { uses: "pnpm/action-setup" },
2215
- step: { uses: "pnpm/action-setup@v4" }
2198
+ step: { uses: "pnpm/action-setup@v5" }
2216
2199
  },
2217
2200
  {
2218
2201
  match: { uses: "actions/setup-node" },
2219
2202
  step: {
2220
- uses: "actions/setup-node@v4",
2203
+ uses: "actions/setup-node@v6",
2221
2204
  with: {
2222
2205
  ...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
2223
2206
  cache: "pnpm"
@@ -2248,14 +2231,29 @@ async function generateCi(ctx) {
2248
2231
  description: "CI workflow not requested"
2249
2232
  };
2250
2233
  const isGitHub = ctx.config.ci === "github";
2234
+ const isForgejo = !isGitHub;
2251
2235
  const isChangesets = ctx.config.releaseStrategy === "changesets";
2252
- const nodeVersionYaml = computeNodeVersionYaml(ctx);
2236
+ const steps = checkSteps(computeNodeVersionYaml(ctx));
2237
+ const content = buildWorkflowYaml({
2238
+ ci: ctx.config.ci,
2239
+ name: "CI",
2240
+ on: {
2241
+ push: {
2242
+ branches: ["main"],
2243
+ "tags-ignore": ["**"]
2244
+ },
2245
+ pull_request: null
2246
+ },
2247
+ enableEmailNotifications: isForgejo,
2248
+ ...isChangesets && { concurrency: CI_CONCURRENCY },
2249
+ jobName: "check",
2250
+ steps
2251
+ });
2253
2252
  const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
2254
- const content = ciWorkflow(nodeVersionYaml, !isGitHub, isChangesets);
2255
2253
  if (ctx.exists(filePath)) {
2256
2254
  const existing = ctx.read(filePath);
2257
2255
  if (existing) {
2258
- let result = mergeWorkflowSteps(existing, "check", requiredCheckSteps(nodeVersionYaml));
2256
+ let result = mergeWorkflowSteps(existing, "check", toRequiredSteps(steps));
2259
2257
  const withTagsIgnore = ensureWorkflowTagsIgnore(result.content);
2260
2258
  result = {
2261
2259
  content: withTagsIgnore.content,
@@ -2760,83 +2758,119 @@ async function generateChangesets(ctx) {
2760
2758
  }
2761
2759
  //#endregion
2762
2760
  //#region src/generators/release-ci.ts
2761
+ /** Common setup steps shared by release-it and simple strategies. */
2763
2762
  function commonSteps(nodeVersionYaml, publishesNpm) {
2764
- return ` - uses: actions/checkout@v4
2765
- with:
2766
- fetch-depth: 0
2767
- - uses: pnpm/action-setup@v4
2768
- - uses: actions/setup-node@v4
2769
- with:
2770
- ${nodeVersionYaml}
2771
- cache: pnpm${publishesNpm ? `\n registry-url: "https://registry.npmjs.org"` : ""}
2772
- - run: pnpm install --frozen-lockfile`;
2773
- }
2774
- function releaseItWorkflow(ci, nodeVersionYaml, publishesNpm) {
2775
- const isGitHub = ci === "github";
2776
- const permissions = isGitHub ? `
2777
- permissions:
2778
- contents: write
2779
- ` : "";
2780
- const tokenEnv = isGitHub ? `GITHUB_TOKEN: \${{ github.token }}` : `RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}`;
2781
- const npmEnv = publishesNpm ? `\n NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}` : "";
2782
- return `${workflowSchemaComment(ci)}name: Release
2783
- on:
2784
- workflow_dispatch:
2785
- ${permissions}
2786
- jobs:
2787
- release:
2788
- runs-on: ubuntu-latest
2789
- steps:
2790
- ${commonSteps(nodeVersionYaml, publishesNpm)}
2791
- - run: pnpm release-it --ci
2792
- env:
2793
- ${tokenEnv}${npmEnv}
2794
- `;
2763
+ const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
2764
+ return [
2765
+ {
2766
+ match: { uses: "actions/checkout" },
2767
+ step: {
2768
+ uses: "actions/checkout@v6",
2769
+ with: { "fetch-depth": 0 }
2770
+ }
2771
+ },
2772
+ {
2773
+ match: { uses: "pnpm/action-setup" },
2774
+ step: { uses: "pnpm/action-setup@v5" }
2775
+ },
2776
+ {
2777
+ match: { uses: "actions/setup-node" },
2778
+ step: {
2779
+ uses: "actions/setup-node@v6",
2780
+ with: {
2781
+ ...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
2782
+ cache: "pnpm",
2783
+ ...publishesNpm && { "registry-url": "https://registry.npmjs.org" }
2784
+ }
2785
+ }
2786
+ },
2787
+ {
2788
+ match: { run: "pnpm install" },
2789
+ step: { run: "pnpm install --frozen-lockfile" }
2790
+ }
2791
+ ];
2795
2792
  }
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) {
2808
- const isGitHub = ci === "github";
2809
- const permissions = isGitHub ? `
2810
- permissions:
2811
- contents: write
2812
- ` : "";
2813
- const gitConfigStep = `
2814
- - name: Configure git
2815
- run: |
2816
- git config user.name "${isGitHub ? "github-actions[bot]" : "forgejo-actions[bot]"}"
2817
- git config user.email "${isGitHub ? "github-actions[bot]@users.noreply.github.com" : "forgejo-actions[bot]@noreply.localhost"}"`;
2818
- const releaseStep = isGitHub ? `
2819
- - name: Release
2820
- env:
2821
- GITHUB_TOKEN: \${{ github.token }}
2822
- run: pnpm exec bst release:simple` : `
2823
- - name: Release
2824
- env:
2825
- FORGEJO_SERVER_URL: \${{ github.server_url }}
2826
- FORGEJO_REPOSITORY: \${{ github.repository }}
2827
- RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}
2828
- run: pnpm exec bst release:simple`;
2829
- const dockerStep = hasDocker ? dockerPublishStep() : "";
2830
- return `${workflowSchemaComment(ci)}name: Release
2831
- on:
2832
- workflow_dispatch:
2833
- ${permissions}
2834
- jobs:
2835
- release:
2836
- runs-on: ubuntu-latest
2837
- steps:
2838
- ${commonSteps(nodeVersionYaml, publishesNpm)}${gitConfigStep}${releaseStep}${dockerStep}
2839
- `;
2793
+ function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
2794
+ const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
2795
+ const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
2796
+ return [...commonSteps(nodeVersionYaml, publishesNpm), {
2797
+ match: { run: "release-it" },
2798
+ step: {
2799
+ run: "pnpm release-it --ci",
2800
+ env: {
2801
+ ...tokenEnv,
2802
+ ...npmEnv
2803
+ }
2804
+ }
2805
+ }];
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
+ }
2842
+ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker) {
2843
+ const releaseStep = {
2844
+ match: { run: "release:simple" },
2845
+ step: {
2846
+ name: "Release",
2847
+ env: ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
2848
+ FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
2849
+ FORGEJO_REPOSITORY: actionsExpr("github.repository"),
2850
+ RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
2851
+ },
2852
+ run: simpleReleaseCommand()
2853
+ }
2854
+ };
2855
+ const dockerStep = {
2856
+ match: { run: "docker:publish" },
2857
+ step: {
2858
+ name: "Publish Docker images",
2859
+ if: "success()",
2860
+ env: {
2861
+ DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
2862
+ DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE"),
2863
+ DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
2864
+ DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD")
2865
+ },
2866
+ run: "pnpm exec bst docker:publish"
2867
+ }
2868
+ };
2869
+ return [
2870
+ ...commonSteps(nodeVersionYaml, publishesNpm),
2871
+ releaseStep,
2872
+ ...hasDocker ? [dockerStep] : []
2873
+ ];
2840
2874
  }
2841
2875
  /** Build the required release step for the check job (changesets). */
2842
2876
  function changesetsReleaseStep(ci, publishesNpm) {
@@ -2870,69 +2904,10 @@ function changesetsReleaseStep(ci, publishesNpm) {
2870
2904
  }
2871
2905
  };
2872
2906
  }
2873
- function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm, hasDocker) {
2874
- const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
2875
- const steps = [
2876
- {
2877
- match: { uses: "actions/checkout" },
2878
- step: {
2879
- uses: "actions/checkout@v4",
2880
- with: { "fetch-depth": 0 }
2881
- }
2882
- },
2883
- {
2884
- match: { uses: "pnpm/action-setup" },
2885
- step: { uses: "pnpm/action-setup@v4" }
2886
- },
2887
- {
2888
- match: { uses: "actions/setup-node" },
2889
- step: {
2890
- uses: "actions/setup-node@v4",
2891
- with: {
2892
- ...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
2893
- cache: "pnpm",
2894
- ...publishesNpm && { "registry-url": "https://registry.npmjs.org" }
2895
- }
2896
- }
2897
- },
2898
- {
2899
- match: { run: "pnpm install" },
2900
- step: { run: "pnpm install --frozen-lockfile" }
2901
- }
2902
- ];
2907
+ function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker) {
2903
2908
  switch (strategy) {
2904
- case "release-it":
2905
- steps.push({
2906
- match: { run: "release-it" },
2907
- step: { run: "pnpm release-it --ci" }
2908
- });
2909
- break;
2910
- case "simple":
2911
- steps.push({
2912
- match: { run: "release:simple" },
2913
- step: { run: "pnpm exec bst release:simple" }
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
- });
2922
- break;
2923
- case "changesets":
2924
- steps.push({
2925
- match: { run: "changeset" },
2926
- step: { run: "pnpm exec bst release:changesets" }
2927
- });
2928
- break;
2929
- }
2930
- return steps;
2931
- }
2932
- function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker) {
2933
- switch (strategy) {
2934
- case "release-it": return releaseItWorkflow(ci, nodeVersionYaml, publishesNpm);
2935
- case "simple": return commitAndTagVersionWorkflow(ci, nodeVersionYaml, publishesNpm, hasDocker);
2909
+ case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
2910
+ case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker);
2936
2911
  default: return null;
2937
2912
  }
2938
2913
  }
@@ -2983,12 +2958,21 @@ async function generateReleaseCi(ctx) {
2983
2958
  const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
2984
2959
  const nodeVersionYaml = computeNodeVersionYaml(ctx);
2985
2960
  const hasDocker = hasDockerPackages(ctx);
2986
- const content = buildWorkflow(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker);
2987
- if (!content) return {
2961
+ const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker);
2962
+ if (!steps) return {
2988
2963
  filePath,
2989
2964
  action: "skipped",
2990
2965
  description: "Release CI workflow not applicable"
2991
2966
  };
2967
+ const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
2968
+ const content = buildWorkflowYaml({
2969
+ ci: ctx.config.ci,
2970
+ name: "Release",
2971
+ on,
2972
+ ...isGitHub && { permissions: { contents: "write" } },
2973
+ jobName: "release",
2974
+ steps
2975
+ });
2992
2976
  if (ctx.exists(workflowPath)) {
2993
2977
  const raw = ctx.read(workflowPath);
2994
2978
  if (raw) {
@@ -3008,7 +2992,7 @@ async function generateReleaseCi(ctx) {
3008
2992
  description: "Release workflow already up to date"
3009
2993
  };
3010
2994
  }
3011
- const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps(ctx.config.releaseStrategy, nodeVersionYaml, publishesNpm, hasDocker));
2995
+ const merged = mergeWorkflowSteps(existing, "release", toRequiredSteps(steps));
3012
2996
  const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
3013
2997
  if (!merged.changed) {
3014
2998
  if (withComment !== raw) {
@@ -3337,7 +3321,7 @@ function generateMigratePrompt(results, config, detected) {
3337
3321
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3338
3322
  sections.push("# Migration Prompt");
3339
3323
  sections.push("");
3340
- sections.push(`_Generated by \`@bensandee/tooling@0.31.0 repo:sync\` on ${timestamp}_`);
3324
+ sections.push(`_Generated by \`@bensandee/tooling@0.33.0 repo:sync\` on ${timestamp}_`);
3341
3325
  sections.push("");
3342
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.");
3343
3327
  sections.push("");
@@ -4345,33 +4329,53 @@ const releaseTriggerCommand = defineCommand({
4345
4329
  name: "release:trigger",
4346
4330
  description: "Trigger the release CI workflow"
4347
4331
  },
4348
- args: { ref: {
4349
- type: "string",
4350
- description: "Git ref to trigger on (default: main)",
4351
- required: false
4352
- } },
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
+ },
4353
4349
  async run({ args }) {
4354
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;
4355
4354
  const resolved = resolveConnection(process.cwd());
4356
- if (resolved.type === "forgejo") await triggerForgejo(resolved.conn, ref);
4357
- else triggerGitHub(ref);
4355
+ if (resolved.type === "forgejo") await triggerForgejo(resolved.conn, ref, inputs);
4356
+ else triggerGitHub(ref, inputs);
4358
4357
  }
4359
4358
  });
4360
- async function triggerForgejo(conn, ref) {
4359
+ async function triggerForgejo(conn, ref, inputs) {
4361
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;
4362
4363
  const res = await fetch(url, {
4363
4364
  method: "POST",
4364
4365
  headers: {
4365
4366
  Authorization: `token ${conn.token}`,
4366
4367
  "Content-Type": "application/json"
4367
4368
  },
4368
- body: JSON.stringify({ ref })
4369
+ body: JSON.stringify(body)
4369
4370
  });
4370
4371
  if (!res.ok) throw new FatalError(`Failed to trigger Forgejo workflow: ${res.status} ${res.statusText}`);
4371
4372
  log$2.info(`Triggered release workflow on Forgejo (ref: ${ref})`);
4372
4373
  }
4373
- function triggerGitHub(ref) {
4374
- 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() });
4375
4379
  if (result.exitCode !== 0) throw new FatalError(`Failed to trigger GitHub workflow: ${result.stderr || result.stdout || "unknown error"}`);
4376
4380
  log$2.info(`Triggered release workflow on GitHub (ref: ${ref})`);
4377
4381
  }
@@ -4473,8 +4477,18 @@ function readVersion(executor, cwd) {
4473
4477
  if (!pkg?.version) throw new FatalError("No version field found in package.json");
4474
4478
  return pkg.version;
4475
4479
  }
4480
+ /** Configure git identity for CI bot commits. */
4481
+ function configureGitIdentity(executor, config) {
4482
+ const isGitHub = config.platform?.type === "github";
4483
+ const name = isGitHub ? "github-actions[bot]" : "forgejo-actions[bot]";
4484
+ const email = isGitHub ? "github-actions[bot]@users.noreply.github.com" : "forgejo-actions[bot]@noreply.localhost";
4485
+ executor.exec(`git config user.name "${name}"`, { cwd: config.cwd });
4486
+ executor.exec(`git config user.email "${email}"`, { cwd: config.cwd });
4487
+ debug(config, `Configured git identity: ${name} <${email}>`);
4488
+ }
4476
4489
  /** Run the full commit-and-tag-version release flow. */
4477
4490
  async function runSimpleRelease(executor, config) {
4491
+ configureGitIdentity(executor, config);
4478
4492
  const command = buildCommand(config);
4479
4493
  log$2.info(`Running: ${command}`);
4480
4494
  const versionResult = executor.exec(command, { cwd: config.cwd });
@@ -5145,7 +5159,7 @@ const dockerCheckCommand = defineCommand({
5145
5159
  const main = defineCommand({
5146
5160
  meta: {
5147
5161
  name: "bst",
5148
- version: "0.31.0",
5162
+ version: "0.33.0",
5149
5163
  description: "Bootstrap and maintain standardized TypeScript project tooling"
5150
5164
  },
5151
5165
  subCommands: {
@@ -5161,7 +5175,7 @@ const main = defineCommand({
5161
5175
  "docker:check": dockerCheckCommand
5162
5176
  }
5163
5177
  });
5164
- console.log(`@bensandee/tooling v0.31.0`);
5178
+ console.log(`@bensandee/tooling v0.33.0`);
5165
5179
  async function run() {
5166
5180
  await runMain(main);
5167
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.31.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"
@@ -40,23 +40,21 @@
40
40
  "@bensandee/common": "0.1.2"
41
41
  },
42
42
  "devDependencies": {
43
- "@types/node": "24.12.0",
44
- "@types/picomatch": "4.0.2",
45
- "tsdown": "0.21.4",
46
- "typescript": "5.9.3",
47
- "vitest": "4.1.0",
48
- "@bensandee/config": "0.9.1"
49
- },
50
- "optionalDependencies": {
51
43
  "@changesets/cli": "2.30.0",
52
44
  "@release-it/bumper": "7.0.5",
45
+ "@types/node": "24.12.0",
46
+ "@types/picomatch": "4.0.2",
53
47
  "commit-and-tag-version": "12.7.0",
54
48
  "knip": "5.87.0",
55
49
  "lefthook": "2.1.4",
56
50
  "oxfmt": "0.41.0",
57
51
  "oxlint": "1.56.0",
58
52
  "prettier": "3.8.1",
59
- "release-it": "19.2.4"
53
+ "release-it": "19.2.4",
54
+ "tsdown": "0.21.4",
55
+ "typescript": "5.9.3",
56
+ "vitest": "4.1.0",
57
+ "@bensandee/config": "0.9.1"
60
58
  },
61
59
  "scripts": {
62
60
  "build": "tsdown",