@bensandee/tooling 0.28.1 → 0.30.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
@@ -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`, `FORGEJO_TOKEN`. |
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. |
package/dist/bin.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { l as createRealExecutor$1, t as runDockerCheck, u as isExecSyncError } from "./check-D41R218h.mjs";
2
+ import { l as createRealExecutor$1, t as runDockerCheck, u as isExecSyncError } from "./check-DMDdHanG.mjs";
3
3
  import { defineCommand, runMain } from "citty";
4
4
  import * as clack from "@clack/prompts";
5
5
  import { isCancel, select } from "@clack/prompts";
6
6
  import path from "node:path";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { execSync } from "node:child_process";
8
9
  import JSON5 from "json5";
9
10
  import { parse } from "jsonc-parser";
10
11
  import { z } from "zod";
11
12
  import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
12
13
  import { isMap, isScalar, isSeq, parse as parse$1, parseDocument, stringify } from "yaml";
13
- import { execSync } from "node:child_process";
14
14
  import picomatch from "picomatch";
15
15
  import { tmpdir } from "node:os";
16
16
  //#region src/utils/log.ts
@@ -130,9 +130,43 @@ function detectProject(targetDir) {
130
130
  hasChangesetsConfig: exists(".changeset/config.json"),
131
131
  hasRepositoryField: !!rootPkg?.repository,
132
132
  hasCommitAndTagVersion: !!rootPkg?.devDependencies?.["commit-and-tag-version"],
133
+ gitRemoteUrl: detectGitRemoteUrl(targetDir),
133
134
  legacyConfigs: detectLegacyConfigs(targetDir)
134
135
  };
135
136
  }
137
+ /** Read git remote origin URL and normalize to HTTPS. */
138
+ function detectGitRemoteUrl(targetDir) {
139
+ try {
140
+ return normalizeGitUrl(execSync("git remote get-url origin", {
141
+ cwd: targetDir,
142
+ stdio: [
143
+ "ignore",
144
+ "pipe",
145
+ "ignore"
146
+ ],
147
+ timeout: 5e3
148
+ }).toString().trim());
149
+ } catch (_error) {
150
+ return null;
151
+ }
152
+ }
153
+ /**
154
+ * Normalize a git remote URL to a clean HTTPS URL.
155
+ * Handles SSH (git@host:owner/repo.git) and HTTPS variants.
156
+ */
157
+ function normalizeGitUrl(raw) {
158
+ const sshMatch = raw.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
159
+ if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
160
+ try {
161
+ const url = new URL(raw);
162
+ url.pathname = url.pathname.replace(/\.git$/, "");
163
+ url.username = "";
164
+ url.password = "";
165
+ return url.toString().replace(/\/$/, "");
166
+ } catch (_error) {
167
+ return null;
168
+ }
169
+ }
136
170
  /** Scan for legacy/conflicting tooling config files. */
137
171
  function detectLegacyConfigs(targetDir) {
138
172
  let entries;
@@ -1327,16 +1361,121 @@ function matchesManagedScript(scriptValue, expectedFragment) {
1327
1361
  const DEPRECATED_SCRIPTS = ["tooling:init", "tooling:update"];
1328
1362
  /** DevDeps that belong in every project (single repo) or per-package (monorepo). */
1329
1363
  const PER_PACKAGE_DEV_DEPS = {
1330
- "@types/node": "25.3.2",
1331
- tsdown: "0.20.3",
1332
- typescript: "5.9.3",
1333
- vitest: "4.0.18"
1364
+ "@types/node": {
1365
+ "@changesets/cli": "2.30.0",
1366
+ "@release-it/bumper": "7.0.5",
1367
+ "commit-and-tag-version": "12.7.0",
1368
+ "knip": "5.87.0",
1369
+ "lefthook": "2.1.4",
1370
+ "oxfmt": "0.41.0",
1371
+ "oxlint": "1.56.0",
1372
+ "prettier": "3.8.1",
1373
+ "release-it": "19.2.4",
1374
+ "@types/node": "24.12.0",
1375
+ "tsdown": "0.21.4",
1376
+ "typescript": "5.9.3",
1377
+ "vitest": "4.1.0",
1378
+ "pnpm": "10.32.1"
1379
+ }["@types/node"],
1380
+ tsdown: {
1381
+ "@changesets/cli": "2.30.0",
1382
+ "@release-it/bumper": "7.0.5",
1383
+ "commit-and-tag-version": "12.7.0",
1384
+ "knip": "5.87.0",
1385
+ "lefthook": "2.1.4",
1386
+ "oxfmt": "0.41.0",
1387
+ "oxlint": "1.56.0",
1388
+ "prettier": "3.8.1",
1389
+ "release-it": "19.2.4",
1390
+ "@types/node": "24.12.0",
1391
+ "tsdown": "0.21.4",
1392
+ "typescript": "5.9.3",
1393
+ "vitest": "4.1.0",
1394
+ "pnpm": "10.32.1"
1395
+ }["tsdown"],
1396
+ typescript: {
1397
+ "@changesets/cli": "2.30.0",
1398
+ "@release-it/bumper": "7.0.5",
1399
+ "commit-and-tag-version": "12.7.0",
1400
+ "knip": "5.87.0",
1401
+ "lefthook": "2.1.4",
1402
+ "oxfmt": "0.41.0",
1403
+ "oxlint": "1.56.0",
1404
+ "prettier": "3.8.1",
1405
+ "release-it": "19.2.4",
1406
+ "@types/node": "24.12.0",
1407
+ "tsdown": "0.21.4",
1408
+ "typescript": "5.9.3",
1409
+ "vitest": "4.1.0",
1410
+ "pnpm": "10.32.1"
1411
+ }["typescript"],
1412
+ vitest: {
1413
+ "@changesets/cli": "2.30.0",
1414
+ "@release-it/bumper": "7.0.5",
1415
+ "commit-and-tag-version": "12.7.0",
1416
+ "knip": "5.87.0",
1417
+ "lefthook": "2.1.4",
1418
+ "oxfmt": "0.41.0",
1419
+ "oxlint": "1.56.0",
1420
+ "prettier": "3.8.1",
1421
+ "release-it": "19.2.4",
1422
+ "@types/node": "24.12.0",
1423
+ "tsdown": "0.21.4",
1424
+ "typescript": "5.9.3",
1425
+ "vitest": "4.1.0",
1426
+ "pnpm": "10.32.1"
1427
+ }["vitest"]
1334
1428
  };
1335
1429
  /** DevDeps that belong at the root regardless of structure. */
1336
1430
  const ROOT_DEV_DEPS = {
1337
- knip: "5.85.0",
1338
- lefthook: "2.1.2",
1339
- oxlint: "1.50.0"
1431
+ knip: {
1432
+ "@changesets/cli": "2.30.0",
1433
+ "@release-it/bumper": "7.0.5",
1434
+ "commit-and-tag-version": "12.7.0",
1435
+ "knip": "5.87.0",
1436
+ "lefthook": "2.1.4",
1437
+ "oxfmt": "0.41.0",
1438
+ "oxlint": "1.56.0",
1439
+ "prettier": "3.8.1",
1440
+ "release-it": "19.2.4",
1441
+ "@types/node": "24.12.0",
1442
+ "tsdown": "0.21.4",
1443
+ "typescript": "5.9.3",
1444
+ "vitest": "4.1.0",
1445
+ "pnpm": "10.32.1"
1446
+ }["knip"],
1447
+ lefthook: {
1448
+ "@changesets/cli": "2.30.0",
1449
+ "@release-it/bumper": "7.0.5",
1450
+ "commit-and-tag-version": "12.7.0",
1451
+ "knip": "5.87.0",
1452
+ "lefthook": "2.1.4",
1453
+ "oxfmt": "0.41.0",
1454
+ "oxlint": "1.56.0",
1455
+ "prettier": "3.8.1",
1456
+ "release-it": "19.2.4",
1457
+ "@types/node": "24.12.0",
1458
+ "tsdown": "0.21.4",
1459
+ "typescript": "5.9.3",
1460
+ "vitest": "4.1.0",
1461
+ "pnpm": "10.32.1"
1462
+ }["lefthook"],
1463
+ oxlint: {
1464
+ "@changesets/cli": "2.30.0",
1465
+ "@release-it/bumper": "7.0.5",
1466
+ "commit-and-tag-version": "12.7.0",
1467
+ "knip": "5.87.0",
1468
+ "lefthook": "2.1.4",
1469
+ "oxfmt": "0.41.0",
1470
+ "oxlint": "1.56.0",
1471
+ "prettier": "3.8.1",
1472
+ "release-it": "19.2.4",
1473
+ "@types/node": "24.12.0",
1474
+ "tsdown": "0.21.4",
1475
+ "typescript": "5.9.3",
1476
+ "vitest": "4.1.0",
1477
+ "pnpm": "10.32.1"
1478
+ }["oxlint"]
1340
1479
  };
1341
1480
  /**
1342
1481
  * Check if a package name is available as a workspace dependency.
@@ -1364,12 +1503,74 @@ const UPDATE_EXCLUDE = new Set(["@types/node"]);
1364
1503
  function addReleaseDeps(deps, config) {
1365
1504
  switch (config.releaseStrategy) {
1366
1505
  case "release-it":
1367
- deps["release-it"] = "18.1.2";
1368
- if (config.structure === "monorepo") deps["@release-it/bumper"] = "7.0.2";
1506
+ deps["release-it"] = {
1507
+ "@changesets/cli": "2.30.0",
1508
+ "@release-it/bumper": "7.0.5",
1509
+ "commit-and-tag-version": "12.7.0",
1510
+ "knip": "5.87.0",
1511
+ "lefthook": "2.1.4",
1512
+ "oxfmt": "0.41.0",
1513
+ "oxlint": "1.56.0",
1514
+ "prettier": "3.8.1",
1515
+ "release-it": "19.2.4",
1516
+ "@types/node": "24.12.0",
1517
+ "tsdown": "0.21.4",
1518
+ "typescript": "5.9.3",
1519
+ "vitest": "4.1.0",
1520
+ "pnpm": "10.32.1"
1521
+ }["release-it"];
1522
+ if (config.structure === "monorepo") deps["@release-it/bumper"] = {
1523
+ "@changesets/cli": "2.30.0",
1524
+ "@release-it/bumper": "7.0.5",
1525
+ "commit-and-tag-version": "12.7.0",
1526
+ "knip": "5.87.0",
1527
+ "lefthook": "2.1.4",
1528
+ "oxfmt": "0.41.0",
1529
+ "oxlint": "1.56.0",
1530
+ "prettier": "3.8.1",
1531
+ "release-it": "19.2.4",
1532
+ "@types/node": "24.12.0",
1533
+ "tsdown": "0.21.4",
1534
+ "typescript": "5.9.3",
1535
+ "vitest": "4.1.0",
1536
+ "pnpm": "10.32.1"
1537
+ }["@release-it/bumper"];
1538
+ break;
1539
+ case "simple":
1540
+ deps["commit-and-tag-version"] = {
1541
+ "@changesets/cli": "2.30.0",
1542
+ "@release-it/bumper": "7.0.5",
1543
+ "commit-and-tag-version": "12.7.0",
1544
+ "knip": "5.87.0",
1545
+ "lefthook": "2.1.4",
1546
+ "oxfmt": "0.41.0",
1547
+ "oxlint": "1.56.0",
1548
+ "prettier": "3.8.1",
1549
+ "release-it": "19.2.4",
1550
+ "@types/node": "24.12.0",
1551
+ "tsdown": "0.21.4",
1552
+ "typescript": "5.9.3",
1553
+ "vitest": "4.1.0",
1554
+ "pnpm": "10.32.1"
1555
+ }["commit-and-tag-version"];
1369
1556
  break;
1370
- case "simple": break;
1371
1557
  case "changesets":
1372
- deps["@changesets/cli"] = "2.29.4";
1558
+ deps["@changesets/cli"] = {
1559
+ "@changesets/cli": "2.30.0",
1560
+ "@release-it/bumper": "7.0.5",
1561
+ "commit-and-tag-version": "12.7.0",
1562
+ "knip": "5.87.0",
1563
+ "lefthook": "2.1.4",
1564
+ "oxfmt": "0.41.0",
1565
+ "oxlint": "1.56.0",
1566
+ "prettier": "3.8.1",
1567
+ "release-it": "19.2.4",
1568
+ "@types/node": "24.12.0",
1569
+ "tsdown": "0.21.4",
1570
+ "typescript": "5.9.3",
1571
+ "vitest": "4.1.0",
1572
+ "pnpm": "10.32.1"
1573
+ }["@changesets/cli"];
1373
1574
  break;
1374
1575
  }
1375
1576
  }
@@ -1378,9 +1579,39 @@ function getAddedDevDepNames(config) {
1378
1579
  const deps = { ...ROOT_DEV_DEPS };
1379
1580
  if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
1380
1581
  deps["@bensandee/config"] = "0.9.1";
1381
- deps["@bensandee/tooling"] = "0.28.1";
1382
- if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
1383
- if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
1582
+ deps["@bensandee/tooling"] = "0.30.0";
1583
+ if (config.formatter === "oxfmt") deps["oxfmt"] = {
1584
+ "@changesets/cli": "2.30.0",
1585
+ "@release-it/bumper": "7.0.5",
1586
+ "commit-and-tag-version": "12.7.0",
1587
+ "knip": "5.87.0",
1588
+ "lefthook": "2.1.4",
1589
+ "oxfmt": "0.41.0",
1590
+ "oxlint": "1.56.0",
1591
+ "prettier": "3.8.1",
1592
+ "release-it": "19.2.4",
1593
+ "@types/node": "24.12.0",
1594
+ "tsdown": "0.21.4",
1595
+ "typescript": "5.9.3",
1596
+ "vitest": "4.1.0",
1597
+ "pnpm": "10.32.1"
1598
+ }["oxfmt"];
1599
+ if (config.formatter === "prettier") deps["prettier"] = {
1600
+ "@changesets/cli": "2.30.0",
1601
+ "@release-it/bumper": "7.0.5",
1602
+ "commit-and-tag-version": "12.7.0",
1603
+ "knip": "5.87.0",
1604
+ "lefthook": "2.1.4",
1605
+ "oxfmt": "0.41.0",
1606
+ "oxlint": "1.56.0",
1607
+ "prettier": "3.8.1",
1608
+ "release-it": "19.2.4",
1609
+ "@types/node": "24.12.0",
1610
+ "tsdown": "0.21.4",
1611
+ "typescript": "5.9.3",
1612
+ "vitest": "4.1.0",
1613
+ "pnpm": "10.32.1"
1614
+ }["prettier"];
1384
1615
  addReleaseDeps(deps, config);
1385
1616
  return Object.keys(deps).filter((name) => !UPDATE_EXCLUDE.has(name));
1386
1617
  }
@@ -1403,10 +1634,40 @@ async function generatePackageJson(ctx) {
1403
1634
  const devDeps = { ...ROOT_DEV_DEPS };
1404
1635
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1405
1636
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
1406
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.28.1";
1637
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.30.0";
1407
1638
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
1408
- if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
1409
- if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
1639
+ if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1640
+ "@changesets/cli": "2.30.0",
1641
+ "@release-it/bumper": "7.0.5",
1642
+ "commit-and-tag-version": "12.7.0",
1643
+ "knip": "5.87.0",
1644
+ "lefthook": "2.1.4",
1645
+ "oxfmt": "0.41.0",
1646
+ "oxlint": "1.56.0",
1647
+ "prettier": "3.8.1",
1648
+ "release-it": "19.2.4",
1649
+ "@types/node": "24.12.0",
1650
+ "tsdown": "0.21.4",
1651
+ "typescript": "5.9.3",
1652
+ "vitest": "4.1.0",
1653
+ "pnpm": "10.32.1"
1654
+ }["oxfmt"];
1655
+ if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
1656
+ "@changesets/cli": "2.30.0",
1657
+ "@release-it/bumper": "7.0.5",
1658
+ "commit-and-tag-version": "12.7.0",
1659
+ "knip": "5.87.0",
1660
+ "lefthook": "2.1.4",
1661
+ "oxfmt": "0.41.0",
1662
+ "oxlint": "1.56.0",
1663
+ "prettier": "3.8.1",
1664
+ "release-it": "19.2.4",
1665
+ "@types/node": "24.12.0",
1666
+ "tsdown": "0.21.4",
1667
+ "typescript": "5.9.3",
1668
+ "vitest": "4.1.0",
1669
+ "pnpm": "10.32.1"
1670
+ }["prettier"];
1410
1671
  addReleaseDeps(devDeps, ctx.config);
1411
1672
  if (existing) {
1412
1673
  const pkg = parsePackageJson(existing);
@@ -1446,6 +1707,13 @@ async function generatePackageJson(ctx) {
1446
1707
  changes.push(`updated devDependency: ${key} to ${value}`);
1447
1708
  }
1448
1709
  pkg.devDependencies = existingDevDeps;
1710
+ if (!pkg.repository) {
1711
+ const repoUrl = detectGitRemoteUrl(ctx.targetDir);
1712
+ if (repoUrl) {
1713
+ pkg.repository = repoUrl;
1714
+ changes.push("added repository from git remote");
1715
+ }
1716
+ }
1449
1717
  if (!pkg["engines"]) {
1450
1718
  pkg["engines"] = { node: ">=24.13.0" };
1451
1719
  changes.push("set engines.node >= 24.13.0");
@@ -1462,15 +1730,32 @@ async function generatePackageJson(ctx) {
1462
1730
  description: changes.join(", ")
1463
1731
  };
1464
1732
  }
1733
+ const repoUrl = detectGitRemoteUrl(ctx.targetDir);
1465
1734
  const pkg = {
1466
1735
  name: ctx.config.name,
1467
1736
  version: "0.1.0",
1468
1737
  private: true,
1738
+ ...repoUrl && { repository: repoUrl },
1469
1739
  type: "module",
1470
1740
  scripts: allScripts,
1471
1741
  devDependencies: devDeps,
1472
1742
  engines: { node: ">=24.13.0" },
1473
- packageManager: "pnpm@10.29.3"
1743
+ packageManager: `pnpm@${{
1744
+ "@changesets/cli": "2.30.0",
1745
+ "@release-it/bumper": "7.0.5",
1746
+ "commit-and-tag-version": "12.7.0",
1747
+ "knip": "5.87.0",
1748
+ "lefthook": "2.1.4",
1749
+ "oxfmt": "0.41.0",
1750
+ "oxlint": "1.56.0",
1751
+ "prettier": "3.8.1",
1752
+ "release-it": "19.2.4",
1753
+ "@types/node": "24.12.0",
1754
+ "tsdown": "0.21.4",
1755
+ "typescript": "5.9.3",
1756
+ "vitest": "4.1.0",
1757
+ "pnpm": "10.32.1"
1758
+ }["pnpm"]}`
1474
1759
  };
1475
1760
  ctx.write(filePath, JSON.stringify(pkg, null, 2) + "\n");
1476
1761
  return {
@@ -2494,7 +2779,7 @@ function releaseItWorkflow(ci, nodeVersionYaml, publishesNpm) {
2494
2779
  permissions:
2495
2780
  contents: write
2496
2781
  ` : "";
2497
- const tokenEnv = isGitHub ? `GITHUB_TOKEN: \${{ github.token }}` : `FORGEJO_TOKEN: \${{ secrets.FORGEJO_TOKEN }}`;
2782
+ const tokenEnv = isGitHub ? `GITHUB_TOKEN: \${{ github.token }}` : `RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}`;
2498
2783
  const npmEnv = publishesNpm ? `\n NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}` : "";
2499
2784
  return `${workflowSchemaComment(ci)}name: Release
2500
2785
  on:
@@ -2530,7 +2815,7 @@ permissions:
2530
2815
  env:
2531
2816
  FORGEJO_SERVER_URL: \${{ github.server_url }}
2532
2817
  FORGEJO_REPOSITORY: \${{ github.repository }}
2533
- FORGEJO_TOKEN: \${{ secrets.FORGEJO_TOKEN }}
2818
+ RELEASE_TOKEN: \${{ secrets.RELEASE_TOKEN }}
2534
2819
  run: pnpm exec bst release:simple`;
2535
2820
  return `${workflowSchemaComment(ci)}name: Release
2536
2821
  on:
@@ -2568,7 +2853,7 @@ function changesetsReleaseStep(ci, publishesNpm) {
2568
2853
  env: {
2569
2854
  FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
2570
2855
  FORGEJO_REPOSITORY: actionsExpr("github.repository"),
2571
- FORGEJO_TOKEN: actionsExpr("secrets.FORGEJO_TOKEN"),
2856
+ RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
2572
2857
  ...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
2573
2858
  },
2574
2859
  run: "pnpm exec bst release:changesets"
@@ -3034,7 +3319,7 @@ function generateMigratePrompt(results, config, detected) {
3034
3319
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3035
3320
  sections.push("# Migration Prompt");
3036
3321
  sections.push("");
3037
- sections.push(`_Generated by \`@bensandee/tooling@0.28.1 repo:sync\` on ${timestamp}_`);
3322
+ sections.push(`_Generated by \`@bensandee/tooling@0.30.0 repo:sync\` on ${timestamp}_`);
3038
3323
  sections.push("");
3039
3324
  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.");
3040
3325
  sections.push("");
@@ -3068,29 +3353,6 @@ function generateMigratePrompt(results, config, detected) {
3068
3353
  }
3069
3354
  sections.push("## Migration tasks");
3070
3355
  sections.push("");
3071
- if (config.releaseStrategy === "simple" && !detected.hasCommitAndTagVersion) {
3072
- sections.push("### Add commit-and-tag-version to devDependencies");
3073
- sections.push("");
3074
- 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.");
3075
- sections.push("");
3076
- sections.push("Run:");
3077
- sections.push("");
3078
- sections.push("```sh");
3079
- sections.push("pnpm add -D -w commit-and-tag-version");
3080
- sections.push("```");
3081
- sections.push("");
3082
- }
3083
- if (config.releaseStrategy !== "none" && !detected.hasRepositoryField) {
3084
- sections.push("### Add repository field to package.json");
3085
- sections.push("");
3086
- sections.push(`The release strategy \`${config.releaseStrategy}\` requires a \`repository\` field in \`package.json\` so that \`release:trigger\` can determine the correct hosting platform (Forgejo vs GitHub).`);
3087
- sections.push("");
3088
- sections.push("Add the appropriate repository URL to `package.json`:");
3089
- sections.push("");
3090
- sections.push("- For Forgejo: `\"repository\": \"https://your-forgejo-instance.com/owner/repo\"`");
3091
- sections.push("- For GitHub: `\"repository\": \"https://github.com/owner/repo\"`");
3092
- sections.push("");
3093
- }
3094
3356
  const legacyToRemove = detected.legacyConfigs.filter((legacy) => !(legacy.tool === "prettier" && config.formatter === "prettier"));
3095
3357
  if (legacyToRemove.length > 0) {
3096
3358
  sections.push("### Remove legacy tooling");
@@ -3919,16 +4181,17 @@ const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
3919
4181
  * Resolve the hosting platform and connection details.
3920
4182
  *
3921
4183
  * Priority:
3922
- * 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, FORGEJO_TOKEN)
4184
+ * 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, RELEASE_TOKEN)
3923
4185
  * 2. `repository` field in package.json (server URL and owner/repo parsed from the URL)
3924
4186
  *
3925
- * For Forgejo, FORGEJO_TOKEN is always required (either from env or explicitly).
4187
+ * For Forgejo, a token is always required. Accepts RELEASE_TOKEN (preferred) or
4188
+ * FORGEJO_TOKEN (legacy, but conflicts with Forgejo Actions' internal auth).
3926
4189
  * If the repository URL hostname is `github.com`, returns `{ type: "github" }`.
3927
4190
  */
3928
4191
  function resolveConnection(cwd) {
3929
4192
  const serverUrl = process.env["FORGEJO_SERVER_URL"];
3930
4193
  const repository = process.env["FORGEJO_REPOSITORY"];
3931
- const token = process.env["FORGEJO_TOKEN"];
4194
+ const token = process.env["RELEASE_TOKEN"] ?? process.env["FORGEJO_TOKEN"];
3932
4195
  if (serverUrl && repository && token) return {
3933
4196
  type: "forgejo",
3934
4197
  conn: {
@@ -3941,13 +4204,13 @@ function resolveConnection(cwd) {
3941
4204
  if (parsed === null) {
3942
4205
  if (serverUrl) {
3943
4206
  if (!repository) throw new FatalError("FORGEJO_REPOSITORY environment variable is required");
3944
- if (!token) throw new FatalError("FORGEJO_TOKEN environment variable is required");
4207
+ if (!token) throw new FatalError("RELEASE_TOKEN environment variable is required");
3945
4208
  }
3946
4209
  return { type: "github" };
3947
4210
  }
3948
4211
  if (parsed.hostname === "github.com") return { type: "github" };
3949
4212
  const resolvedToken = token;
3950
- if (!resolvedToken) throw new FatalError("FORGEJO_TOKEN environment variable is required (server URL and repository were resolved from package.json)");
4213
+ if (!resolvedToken) throw new FatalError("RELEASE_TOKEN environment variable is required (server URL and repository were resolved from package.json)");
3951
4214
  return {
3952
4215
  type: "forgejo",
3953
4216
  conn: {
@@ -3985,6 +4248,18 @@ function parseGitUrl(urlStr) {
3985
4248
  return null;
3986
4249
  }
3987
4250
  }
4251
+ /**
4252
+ * Configure the git remote origin to use a Forgejo token for push auth.
4253
+ *
4254
+ * This is necessary because `actions/checkout` configures git with the automatic
4255
+ * GITHUB_TOKEN, which may not have push permissions. Using RELEASE_TOKEN ensures
4256
+ * pushes use the explicitly provided token.
4257
+ */
4258
+ function configureGitAuth(executor, conn, cwd) {
4259
+ const host = conn.serverUrl.replace(/^https?:\/\//, "");
4260
+ const authUrl = `https://x-access-token:${conn.token}@${host}/${conn.repository}`;
4261
+ executor.exec(`git remote set-url origin ${authUrl}`, { cwd });
4262
+ }
3988
4263
  //#endregion
3989
4264
  //#region src/commands/release-changesets.ts
3990
4265
  const releaseForgejoCommand = defineCommand({
@@ -4035,6 +4310,7 @@ async function runRelease(config, executor) {
4035
4310
  }
4036
4311
  executor.exec("git config user.name \"forgejo-actions[bot]\"", { cwd: config.cwd });
4037
4312
  executor.exec("git config user.email \"forgejo-actions[bot]@noreply.localhost\"", { cwd: config.cwd });
4313
+ configureGitAuth(executor, config, config.cwd);
4038
4314
  const changesetFiles = executor.listChangesetFiles(config.cwd);
4039
4315
  debug(config, `Changeset files found: ${changesetFiles.length > 0 ? changesetFiles.join(", ") : "(none)"}`);
4040
4316
  if (changesetFiles.length > 0) {
@@ -4208,6 +4484,10 @@ async function runSimpleRelease(executor, config) {
4208
4484
  }
4209
4485
  let pushed = false;
4210
4486
  if (!config.noPush) {
4487
+ if (config.platform?.type === "forgejo") {
4488
+ configureGitAuth(executor, config.platform.conn, config.cwd);
4489
+ debug(config, "Configured git remote with RELEASE_TOKEN auth");
4490
+ }
4211
4491
  const branch = executor.exec("git rev-parse --abbrev-ref HEAD", { cwd: config.cwd }).stdout.trim() || "main";
4212
4492
  debug(config, `Pushing to origin/${branch}`);
4213
4493
  const pushResult = executor.exec(`git push --follow-tags origin ${branch}`, { cwd: config.cwd });
@@ -4847,7 +5127,7 @@ const dockerCheckCommand = defineCommand({
4847
5127
  const main = defineCommand({
4848
5128
  meta: {
4849
5129
  name: "bst",
4850
- version: "0.28.1",
5130
+ version: "0.30.0",
4851
5131
  description: "Bootstrap and maintain standardized TypeScript project tooling"
4852
5132
  },
4853
5133
  subCommands: {
@@ -4863,7 +5143,7 @@ const main = defineCommand({
4863
5143
  "docker:check": dockerCheckCommand
4864
5144
  }
4865
5145
  });
4866
- console.log(`@bensandee/tooling v0.28.1`);
5146
+ console.log(`@bensandee/tooling v0.30.0`);
4867
5147
  async function run() {
4868
5148
  await runMain(main);
4869
5149
  process.exit(process.exitCode ?? 0);
@@ -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-D41R218h.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-DMDdHanG.mjs";
2
2
  export { checkHttpHealth, composeCommand, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
package/dist/index.d.mts CHANGED
@@ -101,6 +101,8 @@ interface DetectedProjectState {
101
101
  hasRepositoryField: boolean;
102
102
  /** Whether commit-and-tag-version is in root devDependencies (required for simple release strategy) */
103
103
  hasCommitAndTagVersion: boolean;
104
+ /** Git remote origin URL (for inferring repository field) */
105
+ gitRemoteUrl: string | null;
104
106
  /** Legacy tooling configs found */
105
107
  legacyConfigs: LegacyConfig[];
106
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.28.1",
3
+ "version": "0.30.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "bst": "./dist/bin.mjs"
@@ -42,14 +42,21 @@
42
42
  "devDependencies": {
43
43
  "@types/node": "24.12.0",
44
44
  "@types/picomatch": "4.0.2",
45
- "tsdown": "0.21.2",
45
+ "tsdown": "0.21.4",
46
46
  "typescript": "5.9.3",
47
47
  "vitest": "4.1.0",
48
48
  "@bensandee/config": "0.9.1"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@changesets/cli": "^2.30.0",
52
- "commit-and-tag-version": "^12.6.1"
51
+ "@changesets/cli": "2.30.0",
52
+ "@release-it/bumper": "7.0.5",
53
+ "commit-and-tag-version": "12.7.0",
54
+ "knip": "5.87.0",
55
+ "lefthook": "2.1.4",
56
+ "oxfmt": "0.41.0",
57
+ "oxlint": "1.56.0",
58
+ "prettier": "3.8.1",
59
+ "release-it": "19.2.4"
53
60
  },
54
61
  "scripts": {
55
62
  "build": "tsdown",
@@ -1,5 +1,5 @@
1
- import { z } from "zod";
2
1
  import { execSync } from "node:child_process";
2
+ import { z } from "zod";
3
3
  //#region src/utils/exec.ts
4
4
  /** Type guard for `execSync` errors that carry stdout/stderr/status. */
5
5
  function isExecSyncError(err) {