@bensandee/tooling 0.56.0 → 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +1617 -1580
- package/package.json +6 -6
package/dist/bin.mjs
CHANGED
|
@@ -1575,12 +1575,12 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1575
1575
|
"@release-it/bumper": "7.0.5",
|
|
1576
1576
|
"@types/node": "24.12.2",
|
|
1577
1577
|
"commit-and-tag-version": "12.7.1",
|
|
1578
|
-
"knip": "6.
|
|
1578
|
+
"knip": "6.9.0",
|
|
1579
1579
|
"lefthook": "2.1.6",
|
|
1580
|
-
"oxfmt": "0.
|
|
1581
|
-
"oxlint": "1.
|
|
1580
|
+
"oxfmt": "0.47.0",
|
|
1581
|
+
"oxlint": "1.62.0",
|
|
1582
1582
|
"prettier": "3.8.3",
|
|
1583
|
-
"release-it": "20.0.
|
|
1583
|
+
"release-it": "20.0.1",
|
|
1584
1584
|
"tsdown": "0.21.10",
|
|
1585
1585
|
"typescript": "6.0.3",
|
|
1586
1586
|
"vitest": "4.1.5",
|
|
@@ -1591,12 +1591,12 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1591
1591
|
"@release-it/bumper": "7.0.5",
|
|
1592
1592
|
"@types/node": "24.12.2",
|
|
1593
1593
|
"commit-and-tag-version": "12.7.1",
|
|
1594
|
-
"knip": "6.
|
|
1594
|
+
"knip": "6.9.0",
|
|
1595
1595
|
"lefthook": "2.1.6",
|
|
1596
|
-
"oxfmt": "0.
|
|
1597
|
-
"oxlint": "1.
|
|
1596
|
+
"oxfmt": "0.47.0",
|
|
1597
|
+
"oxlint": "1.62.0",
|
|
1598
1598
|
"prettier": "3.8.3",
|
|
1599
|
-
"release-it": "20.0.
|
|
1599
|
+
"release-it": "20.0.1",
|
|
1600
1600
|
"tsdown": "0.21.10",
|
|
1601
1601
|
"typescript": "6.0.3",
|
|
1602
1602
|
"vitest": "4.1.5",
|
|
@@ -1607,12 +1607,12 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1607
1607
|
"@release-it/bumper": "7.0.5",
|
|
1608
1608
|
"@types/node": "24.12.2",
|
|
1609
1609
|
"commit-and-tag-version": "12.7.1",
|
|
1610
|
-
"knip": "6.
|
|
1610
|
+
"knip": "6.9.0",
|
|
1611
1611
|
"lefthook": "2.1.6",
|
|
1612
|
-
"oxfmt": "0.
|
|
1613
|
-
"oxlint": "1.
|
|
1612
|
+
"oxfmt": "0.47.0",
|
|
1613
|
+
"oxlint": "1.62.0",
|
|
1614
1614
|
"prettier": "3.8.3",
|
|
1615
|
-
"release-it": "20.0.
|
|
1615
|
+
"release-it": "20.0.1",
|
|
1616
1616
|
"tsdown": "0.21.10",
|
|
1617
1617
|
"typescript": "6.0.3",
|
|
1618
1618
|
"vitest": "4.1.5",
|
|
@@ -1623,12 +1623,12 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1623
1623
|
"@release-it/bumper": "7.0.5",
|
|
1624
1624
|
"@types/node": "24.12.2",
|
|
1625
1625
|
"commit-and-tag-version": "12.7.1",
|
|
1626
|
-
"knip": "6.
|
|
1626
|
+
"knip": "6.9.0",
|
|
1627
1627
|
"lefthook": "2.1.6",
|
|
1628
|
-
"oxfmt": "0.
|
|
1629
|
-
"oxlint": "1.
|
|
1628
|
+
"oxfmt": "0.47.0",
|
|
1629
|
+
"oxlint": "1.62.0",
|
|
1630
1630
|
"prettier": "3.8.3",
|
|
1631
|
-
"release-it": "20.0.
|
|
1631
|
+
"release-it": "20.0.1",
|
|
1632
1632
|
"tsdown": "0.21.10",
|
|
1633
1633
|
"typescript": "6.0.3",
|
|
1634
1634
|
"vitest": "4.1.5",
|
|
@@ -1642,12 +1642,12 @@ const ROOT_DEV_DEPS = {
|
|
|
1642
1642
|
"@release-it/bumper": "7.0.5",
|
|
1643
1643
|
"@types/node": "24.12.2",
|
|
1644
1644
|
"commit-and-tag-version": "12.7.1",
|
|
1645
|
-
"knip": "6.
|
|
1645
|
+
"knip": "6.9.0",
|
|
1646
1646
|
"lefthook": "2.1.6",
|
|
1647
|
-
"oxfmt": "0.
|
|
1648
|
-
"oxlint": "1.
|
|
1647
|
+
"oxfmt": "0.47.0",
|
|
1648
|
+
"oxlint": "1.62.0",
|
|
1649
1649
|
"prettier": "3.8.3",
|
|
1650
|
-
"release-it": "20.0.
|
|
1650
|
+
"release-it": "20.0.1",
|
|
1651
1651
|
"tsdown": "0.21.10",
|
|
1652
1652
|
"typescript": "6.0.3",
|
|
1653
1653
|
"vitest": "4.1.5",
|
|
@@ -1658,12 +1658,12 @@ const ROOT_DEV_DEPS = {
|
|
|
1658
1658
|
"@release-it/bumper": "7.0.5",
|
|
1659
1659
|
"@types/node": "24.12.2",
|
|
1660
1660
|
"commit-and-tag-version": "12.7.1",
|
|
1661
|
-
"knip": "6.
|
|
1661
|
+
"knip": "6.9.0",
|
|
1662
1662
|
"lefthook": "2.1.6",
|
|
1663
|
-
"oxfmt": "0.
|
|
1664
|
-
"oxlint": "1.
|
|
1663
|
+
"oxfmt": "0.47.0",
|
|
1664
|
+
"oxlint": "1.62.0",
|
|
1665
1665
|
"prettier": "3.8.3",
|
|
1666
|
-
"release-it": "20.0.
|
|
1666
|
+
"release-it": "20.0.1",
|
|
1667
1667
|
"tsdown": "0.21.10",
|
|
1668
1668
|
"typescript": "6.0.3",
|
|
1669
1669
|
"vitest": "4.1.5",
|
|
@@ -1674,12 +1674,12 @@ const ROOT_DEV_DEPS = {
|
|
|
1674
1674
|
"@release-it/bumper": "7.0.5",
|
|
1675
1675
|
"@types/node": "24.12.2",
|
|
1676
1676
|
"commit-and-tag-version": "12.7.1",
|
|
1677
|
-
"knip": "6.
|
|
1677
|
+
"knip": "6.9.0",
|
|
1678
1678
|
"lefthook": "2.1.6",
|
|
1679
|
-
"oxfmt": "0.
|
|
1680
|
-
"oxlint": "1.
|
|
1679
|
+
"oxfmt": "0.47.0",
|
|
1680
|
+
"oxlint": "1.62.0",
|
|
1681
1681
|
"prettier": "3.8.3",
|
|
1682
|
-
"release-it": "20.0.
|
|
1682
|
+
"release-it": "20.0.1",
|
|
1683
1683
|
"tsdown": "0.21.10",
|
|
1684
1684
|
"typescript": "6.0.3",
|
|
1685
1685
|
"vitest": "4.1.5",
|
|
@@ -1715,12 +1715,12 @@ function addReleaseDeps(deps, config) {
|
|
|
1715
1715
|
"@release-it/bumper": "7.0.5",
|
|
1716
1716
|
"@types/node": "24.12.2",
|
|
1717
1717
|
"commit-and-tag-version": "12.7.1",
|
|
1718
|
-
"knip": "6.
|
|
1718
|
+
"knip": "6.9.0",
|
|
1719
1719
|
"lefthook": "2.1.6",
|
|
1720
|
-
"oxfmt": "0.
|
|
1721
|
-
"oxlint": "1.
|
|
1720
|
+
"oxfmt": "0.47.0",
|
|
1721
|
+
"oxlint": "1.62.0",
|
|
1722
1722
|
"prettier": "3.8.3",
|
|
1723
|
-
"release-it": "20.0.
|
|
1723
|
+
"release-it": "20.0.1",
|
|
1724
1724
|
"tsdown": "0.21.10",
|
|
1725
1725
|
"typescript": "6.0.3",
|
|
1726
1726
|
"vitest": "4.1.5",
|
|
@@ -1731,12 +1731,12 @@ function addReleaseDeps(deps, config) {
|
|
|
1731
1731
|
"@release-it/bumper": "7.0.5",
|
|
1732
1732
|
"@types/node": "24.12.2",
|
|
1733
1733
|
"commit-and-tag-version": "12.7.1",
|
|
1734
|
-
"knip": "6.
|
|
1734
|
+
"knip": "6.9.0",
|
|
1735
1735
|
"lefthook": "2.1.6",
|
|
1736
|
-
"oxfmt": "0.
|
|
1737
|
-
"oxlint": "1.
|
|
1736
|
+
"oxfmt": "0.47.0",
|
|
1737
|
+
"oxlint": "1.62.0",
|
|
1738
1738
|
"prettier": "3.8.3",
|
|
1739
|
-
"release-it": "20.0.
|
|
1739
|
+
"release-it": "20.0.1",
|
|
1740
1740
|
"tsdown": "0.21.10",
|
|
1741
1741
|
"typescript": "6.0.3",
|
|
1742
1742
|
"vitest": "4.1.5",
|
|
@@ -1749,12 +1749,12 @@ function addReleaseDeps(deps, config) {
|
|
|
1749
1749
|
"@release-it/bumper": "7.0.5",
|
|
1750
1750
|
"@types/node": "24.12.2",
|
|
1751
1751
|
"commit-and-tag-version": "12.7.1",
|
|
1752
|
-
"knip": "6.
|
|
1752
|
+
"knip": "6.9.0",
|
|
1753
1753
|
"lefthook": "2.1.6",
|
|
1754
|
-
"oxfmt": "0.
|
|
1755
|
-
"oxlint": "1.
|
|
1754
|
+
"oxfmt": "0.47.0",
|
|
1755
|
+
"oxlint": "1.62.0",
|
|
1756
1756
|
"prettier": "3.8.3",
|
|
1757
|
-
"release-it": "20.0.
|
|
1757
|
+
"release-it": "20.0.1",
|
|
1758
1758
|
"tsdown": "0.21.10",
|
|
1759
1759
|
"typescript": "6.0.3",
|
|
1760
1760
|
"vitest": "4.1.5",
|
|
@@ -1767,12 +1767,12 @@ function addReleaseDeps(deps, config) {
|
|
|
1767
1767
|
"@release-it/bumper": "7.0.5",
|
|
1768
1768
|
"@types/node": "24.12.2",
|
|
1769
1769
|
"commit-and-tag-version": "12.7.1",
|
|
1770
|
-
"knip": "6.
|
|
1770
|
+
"knip": "6.9.0",
|
|
1771
1771
|
"lefthook": "2.1.6",
|
|
1772
|
-
"oxfmt": "0.
|
|
1773
|
-
"oxlint": "1.
|
|
1772
|
+
"oxfmt": "0.47.0",
|
|
1773
|
+
"oxlint": "1.62.0",
|
|
1774
1774
|
"prettier": "3.8.3",
|
|
1775
|
-
"release-it": "20.0.
|
|
1775
|
+
"release-it": "20.0.1",
|
|
1776
1776
|
"tsdown": "0.21.10",
|
|
1777
1777
|
"typescript": "6.0.3",
|
|
1778
1778
|
"vitest": "4.1.5",
|
|
@@ -1800,19 +1800,19 @@ async function generatePackageJson(ctx) {
|
|
|
1800
1800
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1801
1801
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1802
1802
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.2";
|
|
1803
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1803
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.57.1";
|
|
1804
1804
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1805
1805
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1806
1806
|
"@changesets/cli": "2.31.0",
|
|
1807
1807
|
"@release-it/bumper": "7.0.5",
|
|
1808
1808
|
"@types/node": "24.12.2",
|
|
1809
1809
|
"commit-and-tag-version": "12.7.1",
|
|
1810
|
-
"knip": "6.
|
|
1810
|
+
"knip": "6.9.0",
|
|
1811
1811
|
"lefthook": "2.1.6",
|
|
1812
|
-
"oxfmt": "0.
|
|
1813
|
-
"oxlint": "1.
|
|
1812
|
+
"oxfmt": "0.47.0",
|
|
1813
|
+
"oxlint": "1.62.0",
|
|
1814
1814
|
"prettier": "3.8.3",
|
|
1815
|
-
"release-it": "20.0.
|
|
1815
|
+
"release-it": "20.0.1",
|
|
1816
1816
|
"tsdown": "0.21.10",
|
|
1817
1817
|
"typescript": "6.0.3",
|
|
1818
1818
|
"vitest": "4.1.5",
|
|
@@ -1823,12 +1823,12 @@ async function generatePackageJson(ctx) {
|
|
|
1823
1823
|
"@release-it/bumper": "7.0.5",
|
|
1824
1824
|
"@types/node": "24.12.2",
|
|
1825
1825
|
"commit-and-tag-version": "12.7.1",
|
|
1826
|
-
"knip": "6.
|
|
1826
|
+
"knip": "6.9.0",
|
|
1827
1827
|
"lefthook": "2.1.6",
|
|
1828
|
-
"oxfmt": "0.
|
|
1829
|
-
"oxlint": "1.
|
|
1828
|
+
"oxfmt": "0.47.0",
|
|
1829
|
+
"oxlint": "1.62.0",
|
|
1830
1830
|
"prettier": "3.8.3",
|
|
1831
|
-
"release-it": "20.0.
|
|
1831
|
+
"release-it": "20.0.1",
|
|
1832
1832
|
"tsdown": "0.21.10",
|
|
1833
1833
|
"typescript": "6.0.3",
|
|
1834
1834
|
"vitest": "4.1.5",
|
|
@@ -1917,12 +1917,12 @@ async function generatePackageJson(ctx) {
|
|
|
1917
1917
|
"@release-it/bumper": "7.0.5",
|
|
1918
1918
|
"@types/node": "24.12.2",
|
|
1919
1919
|
"commit-and-tag-version": "12.7.1",
|
|
1920
|
-
"knip": "6.
|
|
1920
|
+
"knip": "6.9.0",
|
|
1921
1921
|
"lefthook": "2.1.6",
|
|
1922
|
-
"oxfmt": "0.
|
|
1923
|
-
"oxlint": "1.
|
|
1922
|
+
"oxfmt": "0.47.0",
|
|
1923
|
+
"oxlint": "1.62.0",
|
|
1924
1924
|
"prettier": "3.8.3",
|
|
1925
|
-
"release-it": "20.0.
|
|
1925
|
+
"release-it": "20.0.1",
|
|
1926
1926
|
"tsdown": "0.21.10",
|
|
1927
1927
|
"typescript": "6.0.3",
|
|
1928
1928
|
"vitest": "4.1.5",
|
|
@@ -2104,239 +2104,598 @@ function generateMonorepoPackageTsconfigs(ctx) {
|
|
|
2104
2104
|
return results;
|
|
2105
2105
|
}
|
|
2106
2106
|
//#endregion
|
|
2107
|
-
//#region src/
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2107
|
+
//#region src/coverage-tracking/compare.ts
|
|
2108
|
+
const TOTAL_KEYS = [
|
|
2109
|
+
"lines",
|
|
2110
|
+
"statements",
|
|
2111
|
+
"branches",
|
|
2112
|
+
"functions"
|
|
2113
|
+
];
|
|
2114
|
+
const DEFAULT_TOLERANCE_PP = .1;
|
|
2115
|
+
/** Round to 2 decimal places to keep diff output stable across runs. */
|
|
2116
|
+
function round2$1(n) {
|
|
2117
|
+
return Math.round(n * 100) / 100;
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Compare a current run against a baseline across the four overall totals.
|
|
2121
|
+
* A total passes when current >= baseline OR (baseline - current) <= tolerancePp.
|
|
2122
|
+
*/
|
|
2123
|
+
function compareTotals(baseline, current, tolerancePp = DEFAULT_TOLERANCE_PP) {
|
|
2124
|
+
const totals = TOTAL_KEYS.map((key) => {
|
|
2125
|
+
const b = baseline[key];
|
|
2126
|
+
const c = current[key];
|
|
2127
|
+
return {
|
|
2128
|
+
key,
|
|
2129
|
+
baseline: b,
|
|
2130
|
+
current: c,
|
|
2131
|
+
delta: round2$1(c - b),
|
|
2132
|
+
pass: c >= b || b - c <= tolerancePp
|
|
2133
|
+
};
|
|
2134
|
+
});
|
|
2135
|
+
return {
|
|
2136
|
+
pass: totals.every((t) => t.pass),
|
|
2137
|
+
tolerancePp,
|
|
2138
|
+
totals
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
/** True iff every total is flat-or-up (delta >= 0). Used to gate baseline updates. */
|
|
2142
|
+
function isFlatOrUp(baseline, current) {
|
|
2143
|
+
return TOTAL_KEYS.every((key) => current[key] >= baseline[key]);
|
|
2144
|
+
}
|
|
2145
|
+
//#endregion
|
|
2146
|
+
//#region src/coverage-tracking/parse.ts
|
|
2147
|
+
const TotalEntrySchema = z.object({
|
|
2148
|
+
total: z.number(),
|
|
2149
|
+
covered: z.number(),
|
|
2150
|
+
skipped: z.number().optional(),
|
|
2151
|
+
pct: z.number()
|
|
2118
2152
|
});
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
expect(1 + 1).toBe(2);
|
|
2125
|
-
});
|
|
2153
|
+
const FileEntrySchema = z.object({
|
|
2154
|
+
lines: TotalEntrySchema,
|
|
2155
|
+
statements: TotalEntrySchema,
|
|
2156
|
+
branches: TotalEntrySchema,
|
|
2157
|
+
functions: TotalEntrySchema
|
|
2126
2158
|
});
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2159
|
+
/**
|
|
2160
|
+
* Schema for v8 / istanbul `coverage/coverage-summary.json`. The "total" key
|
|
2161
|
+
* holds the grand totals; every other key is a per-file path → entry mapping.
|
|
2162
|
+
*/
|
|
2163
|
+
const V8CoverageSummarySchema = z.object({ total: FileEntrySchema }).catchall(FileEntrySchema);
|
|
2164
|
+
const TotalsSchema = z.object({
|
|
2165
|
+
lines: z.number(),
|
|
2166
|
+
statements: z.number(),
|
|
2167
|
+
branches: z.number(),
|
|
2168
|
+
functions: z.number()
|
|
2169
|
+
});
|
|
2170
|
+
const PackageEntrySchema = z.object({
|
|
2171
|
+
lines: z.number(),
|
|
2172
|
+
branches: z.number()
|
|
2173
|
+
});
|
|
2174
|
+
/** Coverage record without identity fields — body shape for `coverage-baseline.json`. */
|
|
2175
|
+
const CoverageBaselineSchema = z.object({
|
|
2176
|
+
totals: TotalsSchema,
|
|
2177
|
+
packages: z.record(z.string(), PackageEntrySchema)
|
|
2178
|
+
});
|
|
2179
|
+
/** Full trimmed record — appended to `coverage-history.jsonl` and emitted by `coverage:summary`. */
|
|
2180
|
+
const CoverageRecordSchema = CoverageBaselineSchema.extend({
|
|
2181
|
+
sha: z.string(),
|
|
2182
|
+
timestamp: z.string()
|
|
2183
|
+
});
|
|
2184
|
+
/** Parse a v8 `coverage-summary.json` blob; throws FatalError on malformed input. */
|
|
2185
|
+
function parseV8Summary(raw) {
|
|
2186
|
+
let json;
|
|
2187
|
+
try {
|
|
2188
|
+
json = JSON.parse(raw);
|
|
2189
|
+
} catch (error) {
|
|
2190
|
+
throw new FatalError(`coverage-summary.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
2158
2191
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2192
|
+
const result = V8CoverageSummarySchema.safeParse(json);
|
|
2193
|
+
if (!result.success) throw new FatalError(`coverage-summary.json shape doesn't match v8 / istanbul output: ${result.error.message}`);
|
|
2194
|
+
return result.data;
|
|
2195
|
+
}
|
|
2196
|
+
/** Parse a `coverage-baseline.json` blob; throws FatalError on malformed input. */
|
|
2197
|
+
function parseBaseline(raw) {
|
|
2198
|
+
let json;
|
|
2199
|
+
try {
|
|
2200
|
+
json = JSON.parse(raw);
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
throw new FatalError(`coverage-baseline.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
2166
2203
|
}
|
|
2167
|
-
const
|
|
2168
|
-
if (!
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2204
|
+
const result = CoverageBaselineSchema.safeParse(json);
|
|
2205
|
+
if (!result.success) throw new FatalError(`coverage-baseline.json has unexpected shape: ${result.error.message}`);
|
|
2206
|
+
return result.data;
|
|
2207
|
+
}
|
|
2208
|
+
/** Parse a single line of `coverage-history.jsonl`; returns undefined for malformed lines. */
|
|
2209
|
+
function parseHistoryLine(line) {
|
|
2210
|
+
let json;
|
|
2211
|
+
try {
|
|
2212
|
+
json = JSON.parse(line);
|
|
2213
|
+
} catch (_error) {
|
|
2214
|
+
return;
|
|
2175
2215
|
}
|
|
2176
|
-
|
|
2216
|
+
const result = CoverageRecordSchema.safeParse(json);
|
|
2217
|
+
return result.success ? result.data : void 0;
|
|
2177
2218
|
}
|
|
2178
2219
|
//#endregion
|
|
2179
|
-
//#region src/
|
|
2180
|
-
const
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
extends: [recommended],
|
|
2186
|
-
ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],
|
|
2187
|
-
});
|
|
2188
|
-
`;
|
|
2189
|
-
const CONFIG_PRESET_ONLY = `import { presetRules, recommended } from "@bensandee/config/oxlint";
|
|
2190
|
-
import { defineConfig } from "oxlint";
|
|
2191
|
-
|
|
2192
|
-
export default defineConfig({
|
|
2193
|
-
rules: presetRules,
|
|
2194
|
-
ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],
|
|
2195
|
-
});
|
|
2196
|
-
`;
|
|
2197
|
-
function addIgnorePatterns(content) {
|
|
2198
|
-
if (content.includes("ignorePatterns")) return void 0;
|
|
2199
|
-
const match = content.match(/defineConfig\(\{(\r?\n)/);
|
|
2200
|
-
if (!match) return void 0;
|
|
2201
|
-
const insertPos = (match.index ?? 0) + match[0].length;
|
|
2202
|
-
return content.slice(0, insertPos) + ` ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],\n` + content.slice(insertPos);
|
|
2220
|
+
//#region src/coverage-tracking/history.ts
|
|
2221
|
+
const DEFAULT_HISTORY_BRANCH = "bst/coverage-metrics";
|
|
2222
|
+
const HISTORY_FILE = "coverage-history.jsonl";
|
|
2223
|
+
const MAX_PUSH_ATTEMPTS = 5;
|
|
2224
|
+
function remoteRef(config) {
|
|
2225
|
+
return `refs/remotes/${config.remote ?? "origin"}/${config.historyBranch}`;
|
|
2203
2226
|
}
|
|
2204
|
-
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
action: "skipped",
|
|
2219
|
-
description: "Already configured"
|
|
2220
|
-
};
|
|
2221
|
-
ctx.write(filePath, updated);
|
|
2222
|
-
return {
|
|
2223
|
-
filePath,
|
|
2224
|
-
action: "updated",
|
|
2225
|
-
description: "Added ignorePatterns"
|
|
2226
|
-
};
|
|
2227
|
-
}
|
|
2228
|
-
if (await ctx.confirmOverwrite(filePath) === "skip") return {
|
|
2229
|
-
filePath,
|
|
2230
|
-
action: "skipped",
|
|
2231
|
-
description: "User chose to keep existing"
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
ctx.write(filePath, content);
|
|
2227
|
+
function fetchBranch(executor, config) {
|
|
2228
|
+
const remote = config.remote ?? "origin";
|
|
2229
|
+
if (executor.exec("git", [
|
|
2230
|
+
"fetch",
|
|
2231
|
+
remote,
|
|
2232
|
+
`${config.historyBranch}:${remoteRef(config)}`
|
|
2233
|
+
], { cwd: config.cwd }).exitCode !== 0) return { exists: false };
|
|
2234
|
+
const revParse = executor.exec("git", [
|
|
2235
|
+
"rev-parse",
|
|
2236
|
+
"--quiet",
|
|
2237
|
+
"--verify",
|
|
2238
|
+
remoteRef(config)
|
|
2239
|
+
], { cwd: config.cwd });
|
|
2240
|
+
if (revParse.exitCode !== 0) return { exists: false };
|
|
2235
2241
|
return {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
description: "Generated oxlint.config.ts"
|
|
2242
|
+
exists: true,
|
|
2243
|
+
parentSha: revParse.stdout.trim()
|
|
2239
2244
|
};
|
|
2240
2245
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
/** Extra ignore patterns when a release strategy generates CHANGELOGs. */
|
|
2246
|
-
const RELEASE_IGNORE_PATTERNS = ["**/CHANGELOG.md"];
|
|
2247
|
-
function requiredIgnorePatterns(releaseStrategy) {
|
|
2248
|
-
if (releaseStrategy === "none") return BASE_IGNORE_PATTERNS;
|
|
2249
|
-
return [...BASE_IGNORE_PATTERNS, ...RELEASE_IGNORE_PATTERNS];
|
|
2250
|
-
}
|
|
2251
|
-
const PRETTIER_CONFIG = `{
|
|
2252
|
-
"semi": true,
|
|
2253
|
-
"singleQuote": false,
|
|
2254
|
-
"trailingComma": "all",
|
|
2255
|
-
"tabWidth": 2,
|
|
2256
|
-
"printWidth": 120
|
|
2246
|
+
function readRemoteHistory(executor, config) {
|
|
2247
|
+
const showResult = executor.exec("git", ["show", `${remoteRef(config)}:${HISTORY_FILE}`], { cwd: config.cwd });
|
|
2248
|
+
if (showResult.exitCode !== 0) return "";
|
|
2249
|
+
return showResult.stdout;
|
|
2257
2250
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2251
|
+
function makeBlob(executor, cwd, content) {
|
|
2252
|
+
const result = executor.exec("git", [
|
|
2253
|
+
"hash-object",
|
|
2254
|
+
"-w",
|
|
2255
|
+
"--stdin"
|
|
2256
|
+
], {
|
|
2257
|
+
cwd,
|
|
2258
|
+
input: content
|
|
2259
|
+
});
|
|
2260
|
+
if (result.exitCode !== 0) throw new FatalError(`git hash-object failed: ${result.stderr.trim() || "unknown error"}`);
|
|
2261
|
+
return result.stdout.trim();
|
|
2262
2262
|
}
|
|
2263
|
-
|
|
2264
|
-
const
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2263
|
+
function makeTree(executor, cwd, blobSha) {
|
|
2264
|
+
const treeInput = `100644 blob ${blobSha}\t${HISTORY_FILE}\n`;
|
|
2265
|
+
const result = executor.exec("git", ["mktree"], {
|
|
2266
|
+
cwd,
|
|
2267
|
+
input: treeInput
|
|
2268
|
+
});
|
|
2269
|
+
if (result.exitCode !== 0) throw new FatalError(`git mktree failed: ${result.stderr.trim() || "unknown error"}`);
|
|
2270
|
+
return result.stdout.trim();
|
|
2271
|
+
}
|
|
2272
|
+
function makeCommit(executor, config, treeSha, parentSha, message) {
|
|
2273
|
+
const env = {};
|
|
2274
|
+
if (config.authorName) {
|
|
2275
|
+
env["GIT_AUTHOR_NAME"] = config.authorName;
|
|
2276
|
+
env["GIT_COMMITTER_NAME"] = config.authorName;
|
|
2275
2277
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
jsonValue = JSON.parse(existing);
|
|
2280
|
-
} catch {
|
|
2281
|
-
const config = { ignorePatterns: required };
|
|
2282
|
-
ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
2283
|
-
return {
|
|
2284
|
-
filePath,
|
|
2285
|
-
action: "updated",
|
|
2286
|
-
description: "Replaced malformed .oxfmtrc.json"
|
|
2287
|
-
};
|
|
2278
|
+
if (config.authorEmail) {
|
|
2279
|
+
env["GIT_AUTHOR_EMAIL"] = config.authorEmail;
|
|
2280
|
+
env["GIT_COMMITTER_EMAIL"] = config.authorEmail;
|
|
2288
2281
|
}
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2282
|
+
const args = parentSha ? [
|
|
2283
|
+
"commit-tree",
|
|
2284
|
+
"-p",
|
|
2285
|
+
parentSha,
|
|
2286
|
+
"-m",
|
|
2287
|
+
message,
|
|
2288
|
+
treeSha
|
|
2289
|
+
] : [
|
|
2290
|
+
"commit-tree",
|
|
2291
|
+
"-m",
|
|
2292
|
+
message,
|
|
2293
|
+
treeSha
|
|
2294
|
+
];
|
|
2295
|
+
const result = executor.exec("git", args, {
|
|
2296
|
+
cwd: config.cwd,
|
|
2297
|
+
env
|
|
2298
|
+
});
|
|
2299
|
+
if (result.exitCode !== 0) throw new FatalError(`git commit-tree failed: ${result.stderr.trim() || "unknown error"}`);
|
|
2300
|
+
return result.stdout.trim();
|
|
2301
|
+
}
|
|
2302
|
+
function pushCommit(executor, config, commitSha, parentSha) {
|
|
2303
|
+
const remote = config.remote ?? "origin";
|
|
2304
|
+
const refSpec = `${commitSha}:refs/heads/${config.historyBranch}`;
|
|
2305
|
+
if (parentSha === void 0) return executor.exec("git", [
|
|
2306
|
+
"push",
|
|
2307
|
+
remote,
|
|
2308
|
+
refSpec
|
|
2309
|
+
], { cwd: config.cwd });
|
|
2310
|
+
const lease = `--force-with-lease=refs/heads/${config.historyBranch}:${parentSha}`;
|
|
2311
|
+
return executor.exec("git", [
|
|
2312
|
+
"push",
|
|
2313
|
+
lease,
|
|
2314
|
+
remote,
|
|
2315
|
+
refSpec
|
|
2316
|
+
], { cwd: config.cwd });
|
|
2317
|
+
}
|
|
2318
|
+
function shortSha(sha) {
|
|
2319
|
+
return sha.slice(0, 7);
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Append a coverage record to the orphan branch. Bootstraps the branch on first run.
|
|
2323
|
+
* Uses pure git plumbing (no worktree / no checkout) so the working tree stays untouched.
|
|
2324
|
+
* On push conflict (concurrent record), refetches and rebuilds the commit (up to MAX_PUSH_ATTEMPTS).
|
|
2325
|
+
*/
|
|
2326
|
+
function appendRecord(executor, config, record) {
|
|
2327
|
+
const line = JSON.stringify(record) + "\n";
|
|
2328
|
+
let lastError = "";
|
|
2329
|
+
for (let attempt = 1; attempt <= MAX_PUSH_ATTEMPTS; attempt++) {
|
|
2330
|
+
const fetched = fetchBranch(executor, config);
|
|
2331
|
+
const newContent = (fetched.exists ? readRemoteHistory(executor, config) : "") + line;
|
|
2332
|
+
const blobSha = makeBlob(executor, config.cwd, newContent);
|
|
2333
|
+
const treeSha = makeTree(executor, config.cwd, blobSha);
|
|
2334
|
+
const message = `chore(coverage): record ${shortSha(record.sha)} [skip ci]`;
|
|
2335
|
+
const commitSha = makeCommit(executor, config, treeSha, fetched.parentSha, message);
|
|
2336
|
+
const pushResult = pushCommit(executor, config, commitSha, fetched.parentSha);
|
|
2337
|
+
if (pushResult.exitCode === 0) return {
|
|
2338
|
+
commitSha,
|
|
2339
|
+
bootstrapped: !fetched.exists,
|
|
2340
|
+
attempts: attempt
|
|
2297
2341
|
};
|
|
2342
|
+
lastError = pushResult.stderr.trim() || pushResult.stdout.trim() || "unknown push error";
|
|
2298
2343
|
}
|
|
2299
|
-
|
|
2300
|
-
const ignorePatterns = Array.isArray(parsed["ignorePatterns"]) ? parsed["ignorePatterns"] : [];
|
|
2301
|
-
const existingSet = new Set(ignorePatterns.filter((p) => typeof p === "string"));
|
|
2302
|
-
const missing = required.filter((p) => !existingSet.has(p));
|
|
2303
|
-
if (missing.length === 0) return {
|
|
2304
|
-
filePath,
|
|
2305
|
-
action: "skipped",
|
|
2306
|
-
description: "Existing oxfmt config already has required ignorePatterns"
|
|
2307
|
-
};
|
|
2308
|
-
parsed["ignorePatterns"] = [...ignorePatterns, ...missing];
|
|
2309
|
-
ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
|
|
2310
|
-
return {
|
|
2311
|
-
filePath,
|
|
2312
|
-
action: "updated",
|
|
2313
|
-
description: `Added ignorePatterns: ${missing.join(", ")}`
|
|
2314
|
-
};
|
|
2344
|
+
throw new FatalError(`Failed to push to ${config.historyBranch} after ${MAX_PUSH_ATTEMPTS} attempts: ${lastError}`);
|
|
2315
2345
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2346
|
+
/**
|
|
2347
|
+
* Read all records from the orphan branch (read-only). Performs a shallow fetch and
|
|
2348
|
+
* reads via `git show` — no working-tree disruption, no local branch creation.
|
|
2349
|
+
* Returns an empty array if the branch doesn't exist yet.
|
|
2350
|
+
*/
|
|
2351
|
+
function readRecords(executor, config) {
|
|
2352
|
+
if (!fetchBranch(executor, config).exists) return [];
|
|
2353
|
+
const raw = readRemoteHistory(executor, config);
|
|
2354
|
+
const records = [];
|
|
2355
|
+
for (const line of raw.split("\n")) {
|
|
2356
|
+
if (line.trim() === "") continue;
|
|
2357
|
+
const parsed = parseHistoryLine(line);
|
|
2358
|
+
if (parsed) records.push(parsed);
|
|
2359
|
+
}
|
|
2360
|
+
return records;
|
|
2361
|
+
}
|
|
2362
|
+
//#endregion
|
|
2363
|
+
//#region src/coverage-tracking/config.ts
|
|
2364
|
+
/** Resolve the effective coverage configuration from explicit inputs (testable). */
|
|
2365
|
+
function resolveFromInputs(inputs) {
|
|
2366
|
+
if (inputs.coverage === false) return {
|
|
2367
|
+
enabled: false,
|
|
2368
|
+
historyEnabled: false,
|
|
2369
|
+
historyBranch: DEFAULT_HISTORY_BRANCH,
|
|
2370
|
+
tolerancePp: DEFAULT_TOLERANCE_PP,
|
|
2371
|
+
gateOnPullRequests: false
|
|
2322
2372
|
};
|
|
2323
|
-
|
|
2373
|
+
const enabled = inputs.hasVitestConfig || inputs.setupVitest;
|
|
2374
|
+
const overrides = inputs.coverage ?? {};
|
|
2324
2375
|
return {
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2376
|
+
enabled,
|
|
2377
|
+
historyEnabled: enabled && overrides.history !== "none",
|
|
2378
|
+
historyBranch: overrides.historyBranch ?? "bst/coverage-metrics",
|
|
2379
|
+
tolerancePp: overrides.tolerancePp ?? .1,
|
|
2380
|
+
gateOnPullRequests: overrides.gateOnPullRequests ?? false
|
|
2328
2381
|
};
|
|
2329
2382
|
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Resolve from the real filesystem (used by CLI commands).
|
|
2385
|
+
* Default-on when a vitest config is detected and `.tooling.json#coverage` is not `false`.
|
|
2386
|
+
*/
|
|
2387
|
+
function resolveCoverageConfig(targetDir) {
|
|
2388
|
+
const tooling = safeLoadToolingConfig(targetDir);
|
|
2389
|
+
return resolveFromInputs({
|
|
2390
|
+
hasVitestConfig: existsSync(path.join(targetDir, "vitest.config.ts")) || existsSync(path.join(targetDir, "vitest.config.mts")),
|
|
2391
|
+
setupVitest: tooling?.setupVitest === true,
|
|
2392
|
+
coverage: tooling?.coverage
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
const CoverageOverridesShape = z.object({
|
|
2396
|
+
history: z.enum(["orphan-branch", "none"]).optional(),
|
|
2397
|
+
historyBranch: z.string().optional(),
|
|
2398
|
+
tolerancePp: z.number().nonnegative().optional(),
|
|
2399
|
+
gateOnPullRequests: z.boolean().optional()
|
|
2400
|
+
});
|
|
2401
|
+
const CoverageFieldSchema = z.union([z.literal(false), CoverageOverridesShape]);
|
|
2402
|
+
const ToolingShallowSchema = z.object({
|
|
2403
|
+
setupVitest: z.boolean().optional(),
|
|
2404
|
+
coverage: CoverageFieldSchema.optional()
|
|
2405
|
+
});
|
|
2406
|
+
/** Resolve from a generator context (used by generators for mock-friendly testing). */
|
|
2407
|
+
function resolveCoverageConfigFromContext(reader, fallbackSetupVitest) {
|
|
2408
|
+
const toolingRaw = reader.read(".tooling.json");
|
|
2409
|
+
let setupVitest = fallbackSetupVitest;
|
|
2410
|
+
let coverage;
|
|
2411
|
+
if (toolingRaw) try {
|
|
2412
|
+
const result = ToolingShallowSchema.safeParse(JSON.parse(toolingRaw));
|
|
2413
|
+
if (result.success) {
|
|
2414
|
+
if (typeof result.data.setupVitest === "boolean") setupVitest = result.data.setupVitest;
|
|
2415
|
+
coverage = result.data.coverage;
|
|
2416
|
+
}
|
|
2417
|
+
} catch (_error) {}
|
|
2418
|
+
return resolveFromInputs({
|
|
2419
|
+
hasVitestConfig: reader.exists("vitest.config.ts") || reader.exists("vitest.config.mts"),
|
|
2420
|
+
setupVitest,
|
|
2421
|
+
coverage
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
/** Like loadToolingConfig but returns undefined on any error so commands can degrade gracefully. */
|
|
2425
|
+
function safeLoadToolingConfig(targetDir) {
|
|
2426
|
+
try {
|
|
2427
|
+
return loadToolingConfig(targetDir);
|
|
2428
|
+
} catch (_error) {
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2330
2432
|
//#endregion
|
|
2331
|
-
//#region src/generators/
|
|
2332
|
-
const
|
|
2433
|
+
//#region src/generators/vitest.ts
|
|
2434
|
+
const VITEST_CONFIG = `import { defineConfig } from "vitest/config";
|
|
2333
2435
|
|
|
2334
2436
|
export default defineConfig({
|
|
2335
|
-
|
|
2437
|
+
test: {
|
|
2438
|
+
include: ["test/**/*.test.ts"],
|
|
2439
|
+
coverage: {
|
|
2440
|
+
provider: "v8",
|
|
2441
|
+
reporter: ["text", "html", "lcov", "json-summary"],
|
|
2442
|
+
},
|
|
2443
|
+
},
|
|
2336
2444
|
});
|
|
2337
2445
|
`;
|
|
2338
|
-
|
|
2339
|
-
|
|
2446
|
+
const STARTER_TEST = `import { describe, it, expect } from "vitest";
|
|
2447
|
+
|
|
2448
|
+
describe("example", () => {
|
|
2449
|
+
it("should pass a basic assertion", () => {
|
|
2450
|
+
expect(1 + 1).toBe(2);
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
`;
|
|
2454
|
+
const ROOT_CONFIG_PATHS = ["vitest.config.ts", "vitest.config.mts"];
|
|
2455
|
+
function findJsonSummaryIssue(content) {
|
|
2456
|
+
if (!/\bcoverage\s*:\s*\{/.test(content)) return "missing-coverage";
|
|
2457
|
+
const reporterMatch = /\breporter\s*:\s*\[([\s\S]*?)\]/.exec(content);
|
|
2458
|
+
if (!reporterMatch) return "missing-reporter";
|
|
2459
|
+
if (!reporterMatch[1]?.includes("json-summary")) return "missing-json-summary";
|
|
2460
|
+
}
|
|
2461
|
+
function describeIssue(issue) {
|
|
2462
|
+
switch (issue) {
|
|
2463
|
+
case "missing-coverage": return "no `coverage` block found";
|
|
2464
|
+
case "missing-reporter": return "no `reporter` array inside `coverage` block";
|
|
2465
|
+
case "missing-json-summary": return "`coverage.reporter` does not include \"json-summary\"";
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
function warnIfMissingJsonSummary(ctx) {
|
|
2469
|
+
if (!resolveCoverageConfigFromContext(ctx, ctx.config.setupVitest).enabled) return;
|
|
2470
|
+
for (const rel of ROOT_CONFIG_PATHS) {
|
|
2471
|
+
if (!ctx.exists(rel)) continue;
|
|
2472
|
+
const content = ctx.read(rel);
|
|
2473
|
+
if (content === void 0) continue;
|
|
2474
|
+
const issue = findJsonSummaryIssue(content);
|
|
2475
|
+
if (issue === void 0) return;
|
|
2476
|
+
log.warn(`${rel}: ${describeIssue(issue)} — coverage tracking (bst coverage:summary / coverage:check) requires the "json-summary" reporter to read coverage/coverage-summary.json.`);
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
async function generateVitest(ctx) {
|
|
2481
|
+
const results = [];
|
|
2482
|
+
if (!ctx.config.setupVitest) {
|
|
2483
|
+
warnIfMissingJsonSummary(ctx);
|
|
2484
|
+
return [{
|
|
2485
|
+
filePath: "vitest.config.ts",
|
|
2486
|
+
action: "skipped",
|
|
2487
|
+
description: "Vitest setup not requested"
|
|
2488
|
+
}];
|
|
2489
|
+
}
|
|
2490
|
+
if (ctx.config.structure === "monorepo") {
|
|
2491
|
+
warnIfMissingJsonSummary(ctx);
|
|
2492
|
+
return [{
|
|
2493
|
+
filePath: "vitest.config.ts",
|
|
2494
|
+
action: "skipped",
|
|
2495
|
+
description: "Monorepo: vitest config belongs in individual packages"
|
|
2496
|
+
}];
|
|
2497
|
+
}
|
|
2498
|
+
const configPath = "vitest.config.ts";
|
|
2499
|
+
if (ctx.exists(configPath)) if (ctx.read(configPath) === VITEST_CONFIG) results.push({
|
|
2500
|
+
filePath: configPath,
|
|
2501
|
+
action: "skipped",
|
|
2502
|
+
description: "Config already up to date"
|
|
2503
|
+
});
|
|
2504
|
+
else if (await ctx.confirmOverwrite(configPath) === "skip") results.push({
|
|
2505
|
+
filePath: configPath,
|
|
2506
|
+
action: "skipped",
|
|
2507
|
+
description: "Existing config preserved"
|
|
2508
|
+
});
|
|
2509
|
+
else {
|
|
2510
|
+
ctx.write(configPath, VITEST_CONFIG);
|
|
2511
|
+
results.push({
|
|
2512
|
+
filePath: configPath,
|
|
2513
|
+
action: "updated",
|
|
2514
|
+
description: "Replaced vitest config"
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
else {
|
|
2518
|
+
ctx.write(configPath, VITEST_CONFIG);
|
|
2519
|
+
results.push({
|
|
2520
|
+
filePath: configPath,
|
|
2521
|
+
action: "created",
|
|
2522
|
+
description: "Generated vitest.config.ts"
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
const testPath = "test/example.test.ts";
|
|
2526
|
+
if (!ctx.exists("test")) {
|
|
2527
|
+
ctx.write(testPath, STARTER_TEST);
|
|
2528
|
+
results.push({
|
|
2529
|
+
filePath: testPath,
|
|
2530
|
+
action: "created",
|
|
2531
|
+
description: "Generated starter test file"
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
warnIfMissingJsonSummary(ctx);
|
|
2535
|
+
return results;
|
|
2536
|
+
}
|
|
2537
|
+
//#endregion
|
|
2538
|
+
//#region src/generators/oxlint.ts
|
|
2539
|
+
const IGNORE_PATTERNS_SPREAD = "...(recommended.ignorePatterns ?? [])";
|
|
2540
|
+
const CONFIG_WITH_LINT_RULES = `import recommended from "@bensandee/config/oxlint/recommended";
|
|
2541
|
+
import { defineConfig } from "oxlint";
|
|
2542
|
+
|
|
2543
|
+
export default defineConfig({
|
|
2544
|
+
extends: [recommended],
|
|
2545
|
+
ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],
|
|
2546
|
+
});
|
|
2547
|
+
`;
|
|
2548
|
+
const CONFIG_PRESET_ONLY = `import { presetRules, recommended } from "@bensandee/config/oxlint";
|
|
2549
|
+
import { defineConfig } from "oxlint";
|
|
2550
|
+
|
|
2551
|
+
export default defineConfig({
|
|
2552
|
+
rules: presetRules,
|
|
2553
|
+
ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],
|
|
2554
|
+
});
|
|
2555
|
+
`;
|
|
2556
|
+
function addIgnorePatterns(content) {
|
|
2557
|
+
if (content.includes("ignorePatterns")) return void 0;
|
|
2558
|
+
const match = content.match(/defineConfig\(\{(\r?\n)/);
|
|
2559
|
+
if (!match) return void 0;
|
|
2560
|
+
const insertPos = (match.index ?? 0) + match[0].length;
|
|
2561
|
+
return content.slice(0, insertPos) + ` ignorePatterns: [${IGNORE_PATTERNS_SPREAD}],\n` + content.slice(insertPos);
|
|
2562
|
+
}
|
|
2563
|
+
async function generateOxlint(ctx) {
|
|
2564
|
+
const filePath = "oxlint.config.ts";
|
|
2565
|
+
const content = ctx.config.useEslintPlugin ? CONFIG_WITH_LINT_RULES : CONFIG_PRESET_ONLY;
|
|
2566
|
+
const existing = ctx.read(filePath);
|
|
2567
|
+
if (existing) {
|
|
2568
|
+
if (existing === content) return {
|
|
2569
|
+
filePath,
|
|
2570
|
+
action: "skipped",
|
|
2571
|
+
description: "Already configured"
|
|
2572
|
+
};
|
|
2573
|
+
if (existing.includes("@bensandee/config/oxlint")) {
|
|
2574
|
+
const updated = addIgnorePatterns(existing);
|
|
2575
|
+
if (!updated) return {
|
|
2576
|
+
filePath,
|
|
2577
|
+
action: "skipped",
|
|
2578
|
+
description: "Already configured"
|
|
2579
|
+
};
|
|
2580
|
+
ctx.write(filePath, updated);
|
|
2581
|
+
return {
|
|
2582
|
+
filePath,
|
|
2583
|
+
action: "updated",
|
|
2584
|
+
description: "Added ignorePatterns"
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
if (await ctx.confirmOverwrite(filePath) === "skip") return {
|
|
2588
|
+
filePath,
|
|
2589
|
+
action: "skipped",
|
|
2590
|
+
description: "User chose to keep existing"
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
ctx.write(filePath, content);
|
|
2594
|
+
return {
|
|
2595
|
+
filePath,
|
|
2596
|
+
action: existing ? "updated" : "created",
|
|
2597
|
+
description: "Generated oxlint.config.ts"
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
//#endregion
|
|
2601
|
+
//#region src/generators/formatter.ts
|
|
2602
|
+
/** Ignore patterns that must always be present in .oxfmtrc.json. */
|
|
2603
|
+
const BASE_IGNORE_PATTERNS = [".claude"];
|
|
2604
|
+
/** Extra ignore patterns when a release strategy generates CHANGELOGs. */
|
|
2605
|
+
const RELEASE_IGNORE_PATTERNS = ["**/CHANGELOG.md"];
|
|
2606
|
+
function requiredIgnorePatterns(releaseStrategy) {
|
|
2607
|
+
if (releaseStrategy === "none") return BASE_IGNORE_PATTERNS;
|
|
2608
|
+
return [...BASE_IGNORE_PATTERNS, ...RELEASE_IGNORE_PATTERNS];
|
|
2609
|
+
}
|
|
2610
|
+
const PRETTIER_CONFIG = `{
|
|
2611
|
+
"semi": true,
|
|
2612
|
+
"singleQuote": false,
|
|
2613
|
+
"trailingComma": "all",
|
|
2614
|
+
"tabWidth": 2,
|
|
2615
|
+
"printWidth": 120
|
|
2616
|
+
}
|
|
2617
|
+
`;
|
|
2618
|
+
async function generateFormatter(ctx) {
|
|
2619
|
+
if (ctx.config.formatter === "oxfmt") return generateOxfmt(ctx);
|
|
2620
|
+
return generatePrettier(ctx);
|
|
2621
|
+
}
|
|
2622
|
+
async function generateOxfmt(ctx) {
|
|
2623
|
+
const filePath = ".oxfmtrc.json";
|
|
2624
|
+
const required = requiredIgnorePatterns(ctx.config.releaseStrategy);
|
|
2625
|
+
const existing = ctx.read(filePath);
|
|
2626
|
+
if (!existing) {
|
|
2627
|
+
const config = { ignorePatterns: required };
|
|
2628
|
+
ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
2629
|
+
return {
|
|
2630
|
+
filePath,
|
|
2631
|
+
action: "created",
|
|
2632
|
+
description: "Generated .oxfmtrc.json"
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
const OxfmtConfigSchema = z.record(z.string(), z.unknown());
|
|
2636
|
+
let jsonValue;
|
|
2637
|
+
try {
|
|
2638
|
+
jsonValue = JSON.parse(existing);
|
|
2639
|
+
} catch {
|
|
2640
|
+
const config = { ignorePatterns: required };
|
|
2641
|
+
ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
2642
|
+
return {
|
|
2643
|
+
filePath,
|
|
2644
|
+
action: "updated",
|
|
2645
|
+
description: "Replaced malformed .oxfmtrc.json"
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
const parseResult = OxfmtConfigSchema.safeParse(jsonValue);
|
|
2649
|
+
if (!parseResult.success) {
|
|
2650
|
+
const config = { ignorePatterns: required };
|
|
2651
|
+
ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
2652
|
+
return {
|
|
2653
|
+
filePath,
|
|
2654
|
+
action: "updated",
|
|
2655
|
+
description: "Replaced malformed .oxfmtrc.json"
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
const parsed = parseResult.data;
|
|
2659
|
+
const ignorePatterns = Array.isArray(parsed["ignorePatterns"]) ? parsed["ignorePatterns"] : [];
|
|
2660
|
+
const existingSet = new Set(ignorePatterns.filter((p) => typeof p === "string"));
|
|
2661
|
+
const missing = required.filter((p) => !existingSet.has(p));
|
|
2662
|
+
if (missing.length === 0) return {
|
|
2663
|
+
filePath,
|
|
2664
|
+
action: "skipped",
|
|
2665
|
+
description: "Existing oxfmt config already has required ignorePatterns"
|
|
2666
|
+
};
|
|
2667
|
+
parsed["ignorePatterns"] = [...ignorePatterns, ...missing];
|
|
2668
|
+
ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
|
|
2669
|
+
return {
|
|
2670
|
+
filePath,
|
|
2671
|
+
action: "updated",
|
|
2672
|
+
description: `Added ignorePatterns: ${missing.join(", ")}`
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
async function generatePrettier(ctx) {
|
|
2676
|
+
const filePath = ".prettierrc";
|
|
2677
|
+
if (ctx.exists(filePath)) return {
|
|
2678
|
+
filePath,
|
|
2679
|
+
action: "skipped",
|
|
2680
|
+
description: "Existing prettier config preserved"
|
|
2681
|
+
};
|
|
2682
|
+
ctx.write(filePath, PRETTIER_CONFIG);
|
|
2683
|
+
return {
|
|
2684
|
+
filePath,
|
|
2685
|
+
action: "created",
|
|
2686
|
+
description: "Generated .prettierrc"
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
//#endregion
|
|
2690
|
+
//#region src/generators/tsdown.ts
|
|
2691
|
+
const TSDOWN_CONFIG = `import { defineConfig } from "tsdown";
|
|
2692
|
+
|
|
2693
|
+
export default defineConfig({
|
|
2694
|
+
entry: ["src/index.ts"],
|
|
2695
|
+
});
|
|
2696
|
+
`;
|
|
2697
|
+
async function generateTsdown(ctx) {
|
|
2698
|
+
const filePath = "tsdown.config.ts";
|
|
2340
2699
|
if (ctx.config.structure === "monorepo") return {
|
|
2341
2700
|
filePath,
|
|
2342
2701
|
action: "skipped",
|
|
@@ -2952,1431 +3311,1105 @@ async function generateGitignore(ctx) {
|
|
|
2952
3311
|
action: "skipped",
|
|
2953
3312
|
description: "Already has all standard entries"
|
|
2954
3313
|
};
|
|
2955
|
-
const missingRequired = REQUIRED_ENTRIES.filter((entry) => !existingNormalized.has(normalizeEntry(entry)));
|
|
2956
|
-
const updated = existing.trimEnd() + "\n\n# Added by @bensandee/tooling\n" + missing.join("\n") + "\n";
|
|
2957
|
-
ctx.write(filePath, updated);
|
|
2958
|
-
if (missingRequired.length === 0) return {
|
|
2959
|
-
filePath,
|
|
2960
|
-
action: "skipped",
|
|
2961
|
-
description: "Only optional entries missing"
|
|
2962
|
-
};
|
|
2963
|
-
return {
|
|
2964
|
-
filePath,
|
|
2965
|
-
action: "updated",
|
|
2966
|
-
description: `Appended ${String(missing.length)} missing entries`
|
|
2967
|
-
};
|
|
2968
|
-
}
|
|
2969
|
-
ctx.write(filePath, allEntries.join("\n") + "\n");
|
|
2970
|
-
return {
|
|
2971
|
-
filePath,
|
|
2972
|
-
action: "created",
|
|
2973
|
-
description: "Generated .gitignore"
|
|
2974
|
-
};
|
|
2975
|
-
}
|
|
2976
|
-
//#endregion
|
|
2977
|
-
//#region src/generators/release-ci.ts
|
|
2978
|
-
const DEFAULT_POST_RELEASE_COMMAND = ["pnpm exec bst webhook:send", " \"$POST_RELEASE_HOOK_URL\""].join("");
|
|
2979
|
-
/** Build env vars for hook secrets to inject into a workflow step. */
|
|
2980
|
-
function hookSecretEnv(secrets) {
|
|
2981
|
-
const env = {
|
|
2982
|
-
POST_RELEASE_HOOK_URL: actionsExpr("vars.POST_RELEASE_HOOK_URL || secrets.POST_RELEASE_HOOK_URL"),
|
|
2983
|
-
WEBHOOK_SECRET: actionsExpr("secrets.POST_RELEASE_HOOK_SECRET")
|
|
2984
|
-
};
|
|
2985
|
-
for (const name of secrets ?? []) env[name] = actionsExpr(`secrets.${name}`);
|
|
2986
|
-
return env;
|
|
2987
|
-
}
|
|
2988
|
-
/** Step that runs after release with post-release webhook env wired up. */
|
|
2989
|
-
function postReleaseHookStep(opts) {
|
|
2990
|
-
const ifCondition = opts?.condition ?? (opts?.guardMain ? "github.ref == 'refs/heads/main'" : void 0);
|
|
2991
|
-
return { step: {
|
|
2992
|
-
name: "Post-release hook",
|
|
2993
|
-
...ifCondition && { if: ifCondition },
|
|
2994
|
-
env: hookSecretEnv(opts?.secrets),
|
|
2995
|
-
run: opts?.command ?? DEFAULT_POST_RELEASE_COMMAND
|
|
2996
|
-
} };
|
|
2997
|
-
}
|
|
2998
|
-
/** Common setup steps shared by release-it and simple strategies. */
|
|
2999
|
-
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
3000
|
-
return [
|
|
3001
|
-
{ step: {
|
|
3002
|
-
uses: "actions/checkout@v6",
|
|
3003
|
-
with: { "fetch-depth": 0 }
|
|
3004
|
-
} },
|
|
3005
|
-
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
3006
|
-
{ step: {
|
|
3007
|
-
uses: "actions/setup-node@v6",
|
|
3008
|
-
with: {
|
|
3009
|
-
...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
3010
|
-
cache: "pnpm",
|
|
3011
|
-
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
3012
|
-
}
|
|
3013
|
-
} },
|
|
3014
|
-
{ step: { run: "pnpm install --frozen-lockfile" } }
|
|
3015
|
-
];
|
|
3016
|
-
}
|
|
3017
|
-
function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
|
|
3018
|
-
const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
|
|
3019
|
-
const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
|
|
3020
|
-
return [...commonSteps(nodeVersionYaml, publishesNpm), { step: {
|
|
3021
|
-
run: "pnpm release-it --ci",
|
|
3022
|
-
env: {
|
|
3023
|
-
...tokenEnv,
|
|
3024
|
-
...npmEnv,
|
|
3025
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3026
|
-
}
|
|
3027
|
-
} }];
|
|
3028
|
-
}
|
|
3029
|
-
/** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
|
|
3030
|
-
function simpleWorkflowDispatchTrigger() {
|
|
3031
|
-
return { workflow_dispatch: { inputs: {
|
|
3032
|
-
bump: {
|
|
3033
|
-
description: "Version bump type (default: conventional-commits auto-detect)",
|
|
3034
|
-
required: false,
|
|
3035
|
-
type: "choice",
|
|
3036
|
-
default: "auto",
|
|
3037
|
-
options: [
|
|
3038
|
-
"auto",
|
|
3039
|
-
"major",
|
|
3040
|
-
"minor",
|
|
3041
|
-
"patch",
|
|
3042
|
-
"first-release"
|
|
3043
|
-
]
|
|
3044
|
-
},
|
|
3045
|
-
prerelease: {
|
|
3046
|
-
description: "Create a prerelease with the given tag (e.g., beta, alpha)",
|
|
3047
|
-
required: false,
|
|
3048
|
-
type: "string"
|
|
3049
|
-
}
|
|
3050
|
-
} } };
|
|
3051
|
-
}
|
|
3052
|
-
/** Env vars that forward workflow dispatch inputs to the release:simple CLI. */
|
|
3053
|
-
function simpleReleaseEnv() {
|
|
3054
|
-
return {
|
|
3055
|
-
RELEASE_BUMP: actionsExpr("inputs.bump"),
|
|
3056
|
-
RELEASE_PRERELEASE: actionsExpr("inputs.prerelease")
|
|
3057
|
-
};
|
|
3058
|
-
}
|
|
3059
|
-
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable) {
|
|
3060
|
-
const releaseEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
3061
|
-
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
3062
|
-
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
3063
|
-
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
3064
|
-
};
|
|
3065
|
-
const pushedIf = "steps.release.outputs.pushed == 'true'";
|
|
3066
|
-
const releaseStep = { step: {
|
|
3067
|
-
id: "release",
|
|
3068
|
-
name: "Release",
|
|
3069
|
-
env: {
|
|
3070
|
-
...releaseEnv,
|
|
3071
|
-
...simpleReleaseEnv(),
|
|
3072
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3073
|
-
},
|
|
3074
|
-
run: "pnpm exec bst release:simple"
|
|
3075
|
-
} };
|
|
3076
|
-
const assetsStep = { step: {
|
|
3077
|
-
name: "Upload Release Assets",
|
|
3078
|
-
if: pushedIf,
|
|
3079
|
-
env: releaseEnv,
|
|
3080
|
-
run: "pnpm exec bst release:assets"
|
|
3081
|
-
} };
|
|
3082
|
-
const dockerStep = { step: {
|
|
3083
|
-
name: "Publish Docker images",
|
|
3084
|
-
if: pushedIf,
|
|
3085
|
-
env: dockerPublishEnv(registryDerivable),
|
|
3086
|
-
run: "pnpm exec bst docker:publish"
|
|
3087
|
-
} };
|
|
3088
|
-
return [
|
|
3089
|
-
...commonSteps(nodeVersionYaml, publishesNpm),
|
|
3090
|
-
releaseStep,
|
|
3091
|
-
...hasAssets ? [assetsStep] : [],
|
|
3092
|
-
...hasDocker ? [dockerStep] : []
|
|
3093
|
-
];
|
|
3094
|
-
}
|
|
3095
|
-
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable) {
|
|
3096
|
-
switch (strategy) {
|
|
3097
|
-
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
3098
|
-
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable);
|
|
3099
|
-
default: return null;
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
async function generateReleaseCi(ctx) {
|
|
3103
|
-
const filePath = "release-ci";
|
|
3104
|
-
if (ctx.config.releaseStrategy === "none" || ctx.config.ci === "none") return {
|
|
3105
|
-
filePath,
|
|
3106
|
-
action: "skipped",
|
|
3107
|
-
description: "Release CI workflow not applicable"
|
|
3108
|
-
};
|
|
3109
|
-
const publishesNpm = ctx.config.publishNpm === true;
|
|
3110
|
-
if (ctx.config.releaseStrategy === "changesets") return {
|
|
3111
|
-
filePath,
|
|
3112
|
-
action: "skipped",
|
|
3113
|
-
description: "Release step included in CI workflow"
|
|
3114
|
-
};
|
|
3115
|
-
const isGitHub = ctx.config.ci === "github";
|
|
3116
|
-
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
3117
|
-
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
3118
|
-
const hasDocker = hasDockerPackages(ctx);
|
|
3119
|
-
const hasAssets = hasReleaseAssets(ctx);
|
|
3120
|
-
const registryDerivable = hasRepositoryUrl(ctx);
|
|
3121
|
-
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
3122
|
-
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable);
|
|
3123
|
-
if (!steps) return {
|
|
3124
|
-
filePath,
|
|
3125
|
-
action: "skipped",
|
|
3126
|
-
description: "Release CI workflow not applicable"
|
|
3127
|
-
};
|
|
3128
|
-
if (ctx.config.releaseStrategy === "release-it") steps.push(postReleaseHookStep({
|
|
3129
|
-
command: getPostReleaseHookCommand(ctx),
|
|
3130
|
-
secrets: hookSecrets
|
|
3131
|
-
}));
|
|
3132
|
-
else if (ctx.config.releaseStrategy === "simple") steps.push(postReleaseHookStep({
|
|
3133
|
-
command: getPostReleaseHookCommand(ctx),
|
|
3134
|
-
secrets: hookSecrets,
|
|
3135
|
-
condition: "steps.release.outputs.pushed == 'true'"
|
|
3136
|
-
}));
|
|
3137
|
-
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
3138
|
-
const content = buildWorkflowYaml({
|
|
3139
|
-
ci: ctx.config.ci,
|
|
3140
|
-
name: "Release",
|
|
3141
|
-
on,
|
|
3142
|
-
...isGitHub && { permissions: { contents: "write" } },
|
|
3143
|
-
jobName: "release",
|
|
3144
|
-
steps
|
|
3145
|
-
});
|
|
3146
|
-
const alreadyExists = ctx.exists(workflowPath);
|
|
3147
|
-
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
3148
|
-
if (existing) {
|
|
3149
|
-
if (isToolingIgnored(existing)) return {
|
|
3150
|
-
filePath: workflowPath,
|
|
3151
|
-
action: "skipped",
|
|
3152
|
-
description: "Release workflow has ignore comment"
|
|
3153
|
-
};
|
|
3154
|
-
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
3155
|
-
filePath: workflowPath,
|
|
3156
|
-
action: "skipped",
|
|
3157
|
-
description: "Release workflow already up to date"
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
3161
|
-
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
3162
|
-
ctx.write(workflowPath, final);
|
|
3163
|
-
return {
|
|
3164
|
-
filePath: workflowPath,
|
|
3165
|
-
action: alreadyExists ? "updated" : "created",
|
|
3166
|
-
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
|
-
//#endregion
|
|
3170
|
-
//#region src/generators/ci.ts
|
|
3171
|
-
/** Build the release step for the check job (changesets strategy). */
|
|
3172
|
-
function changesetsReleaseStep(ci, publishesNpm) {
|
|
3173
|
-
if (ci === "github") return { step: {
|
|
3174
|
-
id: "release",
|
|
3175
|
-
uses: "changesets/action@v1",
|
|
3176
|
-
if: "github.ref == 'refs/heads/main'",
|
|
3177
|
-
with: {
|
|
3178
|
-
publish: "pnpm changeset publish",
|
|
3179
|
-
version: "pnpm changeset version"
|
|
3180
|
-
},
|
|
3181
|
-
env: {
|
|
3182
|
-
GITHUB_TOKEN: actionsExpr("github.token"),
|
|
3183
|
-
...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
3184
|
-
}
|
|
3185
|
-
} };
|
|
3186
|
-
return { step: {
|
|
3187
|
-
id: "release",
|
|
3188
|
-
name: "Release",
|
|
3189
|
-
env: {
|
|
3190
|
-
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
3191
|
-
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
3192
|
-
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
3193
|
-
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") },
|
|
3194
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3195
|
-
},
|
|
3196
|
-
run: "pnpm exec bst release:changesets"
|
|
3197
|
-
} };
|
|
3198
|
-
}
|
|
3199
|
-
const CI_CONCURRENCY = {
|
|
3200
|
-
group: `ci-${actionsExpr("github.ref")}`,
|
|
3201
|
-
"cancel-in-progress": actionsExpr("github.ref != 'refs/heads/main'")
|
|
3202
|
-
};
|
|
3203
|
-
function checkSteps(nodeVersionYaml, publishesNpm) {
|
|
3204
|
-
return [
|
|
3205
|
-
{ step: {
|
|
3206
|
-
uses: "actions/checkout@v6",
|
|
3207
|
-
with: { "fetch-depth": 0 }
|
|
3208
|
-
} },
|
|
3209
|
-
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
3210
|
-
{ step: {
|
|
3211
|
-
uses: "actions/setup-node@v6",
|
|
3212
|
-
with: {
|
|
3213
|
-
...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
3214
|
-
cache: "pnpm",
|
|
3215
|
-
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
3216
|
-
}
|
|
3217
|
-
} },
|
|
3218
|
-
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
3219
|
-
{ step: {
|
|
3220
|
-
name: "Run all checks",
|
|
3221
|
-
run: "pnpm ci:check"
|
|
3222
|
-
} }
|
|
3223
|
-
];
|
|
3224
|
-
}
|
|
3225
|
-
/** Resolve the CI workflow filename based on release strategy. */
|
|
3226
|
-
function ciWorkflowPath(ci, releaseStrategy) {
|
|
3227
|
-
return `${ci === "github" ? ".github/workflows" : ".forgejo/workflows"}/${releaseStrategy === "changesets" ? "ci.yml" : "check.yml"}`;
|
|
3228
|
-
}
|
|
3229
|
-
async function generateCi(ctx) {
|
|
3230
|
-
if (ctx.config.ci === "none") return {
|
|
3231
|
-
filePath: "ci",
|
|
3232
|
-
action: "skipped",
|
|
3233
|
-
description: "CI workflow not requested"
|
|
3234
|
-
};
|
|
3235
|
-
const isGitHub = ctx.config.ci === "github";
|
|
3236
|
-
const isForgejo = !isGitHub;
|
|
3237
|
-
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
3238
|
-
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
3239
|
-
const publishesNpm = ctx.config.publishNpm === true;
|
|
3240
|
-
const hookCommand = getPostReleaseHookCommand(ctx);
|
|
3241
|
-
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
3242
|
-
const hasDocker = isChangesets && hasDockerPackages(ctx);
|
|
3243
|
-
const hasAssets = isChangesets && hasReleaseAssets(ctx);
|
|
3244
|
-
const registryDerivable = hasDocker && hasRepositoryUrl(ctx);
|
|
3245
|
-
const publishedIf = "steps.release.outputs.published == 'true'";
|
|
3246
|
-
const releaseEnv = {};
|
|
3247
|
-
if (isGitHub) releaseEnv["GITHUB_TOKEN"] = actionsExpr("github.token");
|
|
3248
|
-
else {
|
|
3249
|
-
releaseEnv["FORGEJO_SERVER_URL"] = actionsExpr("github.server_url");
|
|
3250
|
-
releaseEnv["FORGEJO_REPOSITORY"] = actionsExpr("github.repository");
|
|
3251
|
-
releaseEnv["RELEASE_TOKEN"] = actionsExpr("secrets.RELEASE_TOKEN");
|
|
3252
|
-
}
|
|
3253
|
-
const steps = [
|
|
3254
|
-
...checkSteps(nodeVersionYaml, isChangesets && publishesNpm),
|
|
3255
|
-
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : [],
|
|
3256
|
-
...hasAssets ? [{ step: {
|
|
3257
|
-
name: "Upload Release Assets",
|
|
3258
|
-
if: publishedIf,
|
|
3259
|
-
env: releaseEnv,
|
|
3260
|
-
run: "pnpm exec bst release:assets"
|
|
3261
|
-
} }] : [],
|
|
3262
|
-
...hasDocker ? [{ step: {
|
|
3263
|
-
name: "Publish Docker images",
|
|
3264
|
-
if: publishedIf,
|
|
3265
|
-
env: dockerPublishEnv(registryDerivable),
|
|
3266
|
-
run: "pnpm exec bst docker:publish"
|
|
3267
|
-
} }] : [],
|
|
3268
|
-
...isChangesets ? [postReleaseHookStep({
|
|
3269
|
-
command: hookCommand,
|
|
3270
|
-
secrets: hookSecrets,
|
|
3271
|
-
condition: publishedIf
|
|
3272
|
-
})] : []
|
|
3273
|
-
];
|
|
3274
|
-
const content = buildWorkflowYaml({
|
|
3275
|
-
ci: ctx.config.ci,
|
|
3276
|
-
name: "CI",
|
|
3277
|
-
on: {
|
|
3278
|
-
push: {
|
|
3279
|
-
branches: ["main"],
|
|
3280
|
-
"tags-ignore": ["**"]
|
|
3281
|
-
},
|
|
3282
|
-
pull_request: null
|
|
3283
|
-
},
|
|
3284
|
-
enableEmailNotifications: isForgejo,
|
|
3285
|
-
...isChangesets && { concurrency: CI_CONCURRENCY },
|
|
3286
|
-
jobName: "check",
|
|
3287
|
-
steps
|
|
3288
|
-
});
|
|
3289
|
-
const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
3290
|
-
const alreadyExists = ctx.exists(filePath);
|
|
3291
|
-
const existing = alreadyExists ? ctx.read(filePath) : void 0;
|
|
3292
|
-
if (existing) {
|
|
3293
|
-
if (isToolingIgnored(existing)) return {
|
|
3294
|
-
filePath,
|
|
3295
|
-
action: "skipped",
|
|
3296
|
-
description: "CI workflow has ignore comment"
|
|
3297
|
-
};
|
|
3298
|
-
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
3299
|
-
filePath,
|
|
3300
|
-
action: "skipped",
|
|
3301
|
-
description: "CI workflow already up to date"
|
|
3302
|
-
};
|
|
3303
|
-
}
|
|
3304
|
-
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
3305
|
-
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
3306
|
-
ctx.write(filePath, final);
|
|
3307
|
-
return {
|
|
3308
|
-
filePath,
|
|
3309
|
-
action: alreadyExists ? "updated" : "created",
|
|
3310
|
-
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions CI workflow`
|
|
3311
|
-
};
|
|
3312
|
-
}
|
|
3313
|
-
//#endregion
|
|
3314
|
-
//#region src/generators/knip.ts
|
|
3315
|
-
const KNIP_CONFIG_SINGLE = `import type { KnipConfig } from "knip";
|
|
3316
|
-
|
|
3317
|
-
export default {
|
|
3318
|
-
entry: ["src/index.ts"],
|
|
3319
|
-
project: ["src/**/*.ts"],
|
|
3320
|
-
ignore: ["dist/**", ".claude/hooks/**"],
|
|
3321
|
-
} satisfies KnipConfig;
|
|
3322
|
-
`;
|
|
3323
|
-
const KNIP_CONFIG_MONOREPO = `import type { KnipConfig } from "knip";
|
|
3324
|
-
|
|
3325
|
-
export default {
|
|
3326
|
-
ignore: [".claude/hooks/**"],
|
|
3327
|
-
workspaces: {
|
|
3328
|
-
".": {
|
|
3329
|
-
entry: [],
|
|
3330
|
-
project: [],
|
|
3331
|
-
},
|
|
3332
|
-
"packages/*": {
|
|
3333
|
-
entry: ["src/index.ts", "src/bin.ts"],
|
|
3334
|
-
project: ["src/**/*.ts"],
|
|
3335
|
-
ignore: ["dist/**"],
|
|
3336
|
-
},
|
|
3337
|
-
},
|
|
3338
|
-
} satisfies KnipConfig;
|
|
3339
|
-
`;
|
|
3340
|
-
const REQUIRED_IGNORE = ".claude/hooks/**";
|
|
3341
|
-
/** All known knip config file locations, in priority order. */
|
|
3342
|
-
const KNIP_CONFIG_PATHS = [
|
|
3343
|
-
"knip.config.ts",
|
|
3344
|
-
"knip.config.mts",
|
|
3345
|
-
"knip.json",
|
|
3346
|
-
"knip.jsonc",
|
|
3347
|
-
"knip.ts",
|
|
3348
|
-
"knip.mts"
|
|
3349
|
-
];
|
|
3350
|
-
const JSON_EXTENSIONS = new Set([".json", ".jsonc"]);
|
|
3351
|
-
function isJsonConfig(path) {
|
|
3352
|
-
const dot = path.lastIndexOf(".");
|
|
3353
|
-
return dot !== -1 && JSON_EXTENSIONS.has(path.slice(dot));
|
|
3354
|
-
}
|
|
3355
|
-
/**
|
|
3356
|
-
* Try to add the required ignore pattern to an existing knip config.
|
|
3357
|
-
* Returns the updated content, or undefined if no change was needed.
|
|
3358
|
-
*/
|
|
3359
|
-
function ensureIgnorePattern(content, path) {
|
|
3360
|
-
if (content.includes(".claude/hooks")) return void 0;
|
|
3361
|
-
if (isJsonConfig(path)) return ensureIgnoreInJson(content);
|
|
3362
|
-
return ensureIgnoreInTs(content);
|
|
3363
|
-
}
|
|
3364
|
-
const KnipJsonSchema = z.object({ ignore: z.array(z.string()).optional() }).passthrough();
|
|
3365
|
-
function ensureIgnoreInJson(content) {
|
|
3366
|
-
const result = KnipJsonSchema.safeParse(JSON.parse(content));
|
|
3367
|
-
if (!result.success) return void 0;
|
|
3368
|
-
const parsed = result.data;
|
|
3369
|
-
const ignore = parsed.ignore ?? [];
|
|
3370
|
-
ignore.push(REQUIRED_IGNORE);
|
|
3371
|
-
parsed.ignore = ignore;
|
|
3372
|
-
return JSON.stringify(parsed, null, 2) + "\n";
|
|
3373
|
-
}
|
|
3374
|
-
function ensureIgnoreInTs(content) {
|
|
3375
|
-
const ignoreArrayPattern = /(ignore:\s*\[)/;
|
|
3376
|
-
if (ignoreArrayPattern.exec(content)) return content.replace(ignoreArrayPattern, `$1"${REQUIRED_IGNORE}", `);
|
|
3377
|
-
const exportPattern = /(export default\s*\{)\n/;
|
|
3378
|
-
if (exportPattern.exec(content)) return content.replace(exportPattern, `$1\n ignore: ["${REQUIRED_IGNORE}"],\n`);
|
|
3379
|
-
}
|
|
3380
|
-
async function generateKnip(ctx) {
|
|
3381
|
-
const filePath = "knip.config.ts";
|
|
3382
|
-
const isMonorepo = ctx.config.structure === "monorepo";
|
|
3383
|
-
const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
3384
|
-
if (existingPath) {
|
|
3385
|
-
const content = ctx.read(existingPath);
|
|
3386
|
-
if (content) {
|
|
3387
|
-
const updated = ensureIgnorePattern(content, existingPath);
|
|
3388
|
-
if (updated) {
|
|
3389
|
-
ctx.write(existingPath, updated);
|
|
3390
|
-
return {
|
|
3391
|
-
filePath: existingPath,
|
|
3392
|
-
action: "updated",
|
|
3393
|
-
description: "Added \".claude/hooks/**\" to ignore"
|
|
3394
|
-
};
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
return {
|
|
3398
|
-
filePath: existingPath,
|
|
3399
|
-
action: "skipped",
|
|
3400
|
-
description: existingPath === filePath ? "Already configured" : `Existing config found at ${existingPath}`
|
|
3401
|
-
};
|
|
3402
|
-
}
|
|
3403
|
-
const config = isMonorepo ? KNIP_CONFIG_MONOREPO : KNIP_CONFIG_SINGLE;
|
|
3404
|
-
ctx.write(filePath, config);
|
|
3405
|
-
return {
|
|
3406
|
-
filePath,
|
|
3407
|
-
action: "created",
|
|
3408
|
-
description: "Generated knip.config.ts for dead code analysis"
|
|
3409
|
-
};
|
|
3410
|
-
}
|
|
3411
|
-
//#endregion
|
|
3412
|
-
//#region src/generators/renovate.ts
|
|
3413
|
-
const SHARED_PRESET = "local>bensandee/tooling";
|
|
3414
|
-
/** Deprecated npm-based preset to migrate away from. */
|
|
3415
|
-
const LEGACY_PRESET = "@bensandee/config";
|
|
3416
|
-
/** All known renovate config file locations, in priority order. */
|
|
3417
|
-
const RENOVATE_CONFIG_PATHS = [
|
|
3418
|
-
"renovate.json",
|
|
3419
|
-
"renovate.json5",
|
|
3420
|
-
".renovaterc",
|
|
3421
|
-
".renovaterc.json",
|
|
3422
|
-
".github/renovate.json",
|
|
3423
|
-
".github/renovate.json5"
|
|
3424
|
-
];
|
|
3425
|
-
async function generateRenovate(ctx) {
|
|
3426
|
-
const defaultPath = "renovate.json";
|
|
3427
|
-
if (!ctx.config.setupRenovate) return {
|
|
3428
|
-
filePath: defaultPath,
|
|
3429
|
-
action: "skipped",
|
|
3430
|
-
description: "Renovate not requested"
|
|
3431
|
-
};
|
|
3432
|
-
const existingPath = RENOVATE_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
3433
|
-
if (existingPath) {
|
|
3434
|
-
const existing = ctx.read(existingPath);
|
|
3435
|
-
if (existing) {
|
|
3436
|
-
const parsed = parseRenovateJson(existing);
|
|
3437
|
-
const existingExtends = parsed.extends ?? [];
|
|
3438
|
-
const legacyIndex = existingExtends.indexOf(LEGACY_PRESET);
|
|
3439
|
-
if (legacyIndex !== -1) {
|
|
3440
|
-
existingExtends[legacyIndex] = SHARED_PRESET;
|
|
3441
|
-
parsed.extends = existingExtends;
|
|
3442
|
-
ctx.write(existingPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
3443
|
-
return {
|
|
3444
|
-
filePath: existingPath,
|
|
3445
|
-
action: "updated",
|
|
3446
|
-
description: `Migrated extends: ${LEGACY_PRESET} → ${SHARED_PRESET}`
|
|
3447
|
-
};
|
|
3448
|
-
}
|
|
3449
|
-
if (!existingExtends.includes(SHARED_PRESET)) {
|
|
3450
|
-
existingExtends.unshift(SHARED_PRESET);
|
|
3451
|
-
parsed.extends = existingExtends;
|
|
3452
|
-
ctx.write(existingPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
3453
|
-
return {
|
|
3454
|
-
filePath: existingPath,
|
|
3455
|
-
action: "updated",
|
|
3456
|
-
description: `Added extends: ${SHARED_PRESET}`
|
|
3457
|
-
};
|
|
3458
|
-
}
|
|
3459
|
-
return {
|
|
3460
|
-
filePath: existingPath,
|
|
3461
|
-
action: "skipped",
|
|
3462
|
-
description: "Already extends shared config"
|
|
3463
|
-
};
|
|
3464
|
-
}
|
|
3314
|
+
const missingRequired = REQUIRED_ENTRIES.filter((entry) => !existingNormalized.has(normalizeEntry(entry)));
|
|
3315
|
+
const updated = existing.trimEnd() + "\n\n# Added by @bensandee/tooling\n" + missing.join("\n") + "\n";
|
|
3316
|
+
ctx.write(filePath, updated);
|
|
3317
|
+
if (missingRequired.length === 0) return {
|
|
3318
|
+
filePath,
|
|
3319
|
+
action: "skipped",
|
|
3320
|
+
description: "Only optional entries missing"
|
|
3321
|
+
};
|
|
3322
|
+
return {
|
|
3323
|
+
filePath,
|
|
3324
|
+
action: "updated",
|
|
3325
|
+
description: `Appended ${String(missing.length)} missing entries`
|
|
3326
|
+
};
|
|
3465
3327
|
}
|
|
3466
|
-
|
|
3467
|
-
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
|
3468
|
-
extends: [SHARED_PRESET]
|
|
3469
|
-
};
|
|
3470
|
-
ctx.write(defaultPath, JSON.stringify(config, null, 2) + "\n");
|
|
3328
|
+
ctx.write(filePath, allEntries.join("\n") + "\n");
|
|
3471
3329
|
return {
|
|
3472
|
-
filePath
|
|
3330
|
+
filePath,
|
|
3473
3331
|
action: "created",
|
|
3474
|
-
description: "Generated
|
|
3332
|
+
description: "Generated .gitignore"
|
|
3475
3333
|
};
|
|
3476
3334
|
}
|
|
3477
3335
|
//#endregion
|
|
3478
|
-
//#region src/generators/
|
|
3479
|
-
const
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
filePath,
|
|
3486
|
-
action: "skipped",
|
|
3487
|
-
description: "Not a monorepo"
|
|
3488
|
-
};
|
|
3489
|
-
if (ctx.exists(filePath)) return {
|
|
3490
|
-
filePath,
|
|
3491
|
-
action: "skipped",
|
|
3492
|
-
description: "pnpm-workspace.yaml already exists"
|
|
3336
|
+
//#region src/generators/release-ci.ts
|
|
3337
|
+
const DEFAULT_POST_RELEASE_COMMAND = ["pnpm exec bst webhook:send", " \"$POST_RELEASE_HOOK_URL\""].join("");
|
|
3338
|
+
/** Build env vars for hook secrets to inject into a workflow step. */
|
|
3339
|
+
function hookSecretEnv(secrets) {
|
|
3340
|
+
const env = {
|
|
3341
|
+
POST_RELEASE_HOOK_URL: actionsExpr("vars.POST_RELEASE_HOOK_URL || secrets.POST_RELEASE_HOOK_URL"),
|
|
3342
|
+
WEBHOOK_SECRET: actionsExpr("secrets.POST_RELEASE_HOOK_SECRET")
|
|
3493
3343
|
};
|
|
3494
|
-
|
|
3344
|
+
for (const name of secrets ?? []) env[name] = actionsExpr(`secrets.${name}`);
|
|
3345
|
+
return env;
|
|
3346
|
+
}
|
|
3347
|
+
/** Step that runs after release with post-release webhook env wired up. */
|
|
3348
|
+
function postReleaseHookStep(opts) {
|
|
3349
|
+
const ifCondition = opts?.condition ?? (opts?.guardMain ? "github.ref == 'refs/heads/main'" : void 0);
|
|
3350
|
+
return { step: {
|
|
3351
|
+
name: "Post-release hook",
|
|
3352
|
+
...ifCondition && { if: ifCondition },
|
|
3353
|
+
env: hookSecretEnv(opts?.secrets),
|
|
3354
|
+
run: opts?.command ?? DEFAULT_POST_RELEASE_COMMAND
|
|
3355
|
+
} };
|
|
3356
|
+
}
|
|
3357
|
+
/** Common setup steps shared by release-it and simple strategies. */
|
|
3358
|
+
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
3359
|
+
return [
|
|
3360
|
+
{ step: {
|
|
3361
|
+
uses: "actions/checkout@v6",
|
|
3362
|
+
with: { "fetch-depth": 0 }
|
|
3363
|
+
} },
|
|
3364
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
3365
|
+
{ step: {
|
|
3366
|
+
uses: "actions/setup-node@v6",
|
|
3367
|
+
with: {
|
|
3368
|
+
...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
3369
|
+
cache: "pnpm",
|
|
3370
|
+
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
3371
|
+
}
|
|
3372
|
+
} },
|
|
3373
|
+
{ step: { run: "pnpm install --frozen-lockfile" } }
|
|
3374
|
+
];
|
|
3375
|
+
}
|
|
3376
|
+
function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
|
|
3377
|
+
const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
|
|
3378
|
+
const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
|
|
3379
|
+
return [...commonSteps(nodeVersionYaml, publishesNpm), { step: {
|
|
3380
|
+
run: "pnpm release-it --ci",
|
|
3381
|
+
env: {
|
|
3382
|
+
...tokenEnv,
|
|
3383
|
+
...npmEnv,
|
|
3384
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3385
|
+
}
|
|
3386
|
+
} }];
|
|
3387
|
+
}
|
|
3388
|
+
/** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
|
|
3389
|
+
function simpleWorkflowDispatchTrigger() {
|
|
3390
|
+
return { workflow_dispatch: { inputs: {
|
|
3391
|
+
bump: {
|
|
3392
|
+
description: "Version bump type (default: conventional-commits auto-detect)",
|
|
3393
|
+
required: false,
|
|
3394
|
+
type: "choice",
|
|
3395
|
+
default: "auto",
|
|
3396
|
+
options: [
|
|
3397
|
+
"auto",
|
|
3398
|
+
"major",
|
|
3399
|
+
"minor",
|
|
3400
|
+
"patch",
|
|
3401
|
+
"first-release"
|
|
3402
|
+
]
|
|
3403
|
+
},
|
|
3404
|
+
prerelease: {
|
|
3405
|
+
description: "Create a prerelease with the given tag (e.g., beta, alpha)",
|
|
3406
|
+
required: false,
|
|
3407
|
+
type: "string"
|
|
3408
|
+
}
|
|
3409
|
+
} } };
|
|
3410
|
+
}
|
|
3411
|
+
/** Env vars that forward workflow dispatch inputs to the release:simple CLI. */
|
|
3412
|
+
function simpleReleaseEnv() {
|
|
3495
3413
|
return {
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
description: "Generated pnpm-workspace.yaml"
|
|
3414
|
+
RELEASE_BUMP: actionsExpr("inputs.bump"),
|
|
3415
|
+
RELEASE_PRERELEASE: actionsExpr("inputs.prerelease")
|
|
3499
3416
|
};
|
|
3500
3417
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3418
|
+
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable) {
|
|
3419
|
+
const releaseEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
3420
|
+
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
3421
|
+
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
3422
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
3423
|
+
};
|
|
3424
|
+
const pushedIf = "steps.release.outputs.pushed == 'true'";
|
|
3425
|
+
const releaseStep = { step: {
|
|
3426
|
+
id: "release",
|
|
3427
|
+
name: "Release",
|
|
3428
|
+
env: {
|
|
3429
|
+
...releaseEnv,
|
|
3430
|
+
...simpleReleaseEnv(),
|
|
3431
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3432
|
+
},
|
|
3433
|
+
run: "pnpm exec bst release:simple"
|
|
3434
|
+
} };
|
|
3435
|
+
const assetsStep = { step: {
|
|
3436
|
+
name: "Upload Release Assets",
|
|
3437
|
+
if: pushedIf,
|
|
3438
|
+
env: releaseEnv,
|
|
3439
|
+
run: "pnpm exec bst release:assets"
|
|
3440
|
+
} };
|
|
3441
|
+
const dockerStep = { step: {
|
|
3442
|
+
name: "Publish Docker images",
|
|
3443
|
+
if: pushedIf,
|
|
3444
|
+
env: dockerPublishEnv(registryDerivable),
|
|
3445
|
+
run: "pnpm exec bst docker:publish"
|
|
3446
|
+
} };
|
|
3447
|
+
return [
|
|
3448
|
+
...commonSteps(nodeVersionYaml, publishesNpm),
|
|
3449
|
+
releaseStep,
|
|
3450
|
+
...hasAssets ? [assetsStep] : [],
|
|
3451
|
+
...hasDocker ? [dockerStep] : []
|
|
3452
|
+
];
|
|
3453
|
+
}
|
|
3454
|
+
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable) {
|
|
3455
|
+
switch (strategy) {
|
|
3456
|
+
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
3457
|
+
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable);
|
|
3458
|
+
default: return null;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
async function generateReleaseCi(ctx) {
|
|
3462
|
+
const filePath = "release-ci";
|
|
3463
|
+
if (ctx.config.releaseStrategy === "none" || ctx.config.ci === "none") return {
|
|
3508
3464
|
filePath,
|
|
3509
3465
|
action: "skipped",
|
|
3510
|
-
description:
|
|
3466
|
+
description: "Release CI workflow not applicable"
|
|
3511
3467
|
};
|
|
3512
|
-
ctx.
|
|
3513
|
-
return {
|
|
3468
|
+
const publishesNpm = ctx.config.publishNpm === true;
|
|
3469
|
+
if (ctx.config.releaseStrategy === "changesets") return {
|
|
3514
3470
|
filePath,
|
|
3515
|
-
action:
|
|
3516
|
-
description:
|
|
3471
|
+
action: "skipped",
|
|
3472
|
+
description: "Release step included in CI workflow"
|
|
3517
3473
|
};
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.7.2\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport * as fs from \"node:fs\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execFileSync } from \"node:child_process\";\nimport * as os from \"node:os\";\n\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/core.js\n/** A special constant with type `never` */\nconst NEVER = Object.freeze({ status: \"aborted\" });\nfunction $constructor(name, initializer, params) {\n function init(inst, def) {\n if (!inst._zod) Object.defineProperty(inst, \"_zod\", {\n value: {\n def,\n constr: _,\n traits: /* @__PURE__ */ new Set()\n },\n enumerable: false\n });\n if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n const proto = _.prototype;\n const keys = Object.keys(proto);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i];\n if (!(k in inst)) inst[k] = proto[k].bind(inst);\n }\n }\n const Parent = params?.Parent ?? Object;\n class Definition extends Parent {}\n Object.defineProperty(Definition, \"name\", { value: name });\n function _(def) {\n var _a;\n const inst = params?.Parent ? new Definition() : this;\n init(inst, def);\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n for (const fn of inst._zod.deferred) fn();\n return inst;\n }\n Object.defineProperty(_, \"init\", { value: init });\n Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {\n if (params?.Parent && inst instanceof params.Parent) return true;\n return inst?._zod?.traits?.has(name);\n } });\n Object.defineProperty(_, \"name\", { value: name });\n return _;\n}\nvar $ZodAsyncError = class extends Error {\n constructor() {\n super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);\n }\n};\nvar $ZodEncodeError = class extends Error {\n constructor(name) {\n super(`Encountered unidirectional transform during encode: ${name}`);\n this.name = \"ZodEncodeError\";\n }\n};\nconst globalConfig = {};\nfunction config(newConfig) {\n if (newConfig) Object.assign(globalConfig, newConfig);\n return globalConfig;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/util.js\nfunction getEnumValues(entries) {\n const numericValues = Object.values(entries).filter((v) => typeof v === \"number\");\n return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);\n}\nfunction jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nfunction cached(getter) {\n return { get value() {\n {\n const value = getter();\n Object.defineProperty(this, \"value\", { value });\n return value;\n }\n throw new Error(\"cached value already set\");\n } };\n}\nfunction nullish(input) {\n return input === null || input === void 0;\n}\nfunction cleanRegex(source) {\n const start = source.startsWith(\"^\") ? 1 : 0;\n const end = source.endsWith(\"$\") ? source.length - 1 : source.length;\n return source.slice(start, end);\n}\nconst EVALUATING = Symbol(\"evaluating\");\nfunction defineLazy(object, key, getter) {\n let value = void 0;\n Object.defineProperty(object, key, {\n get() {\n if (value === EVALUATING) return;\n if (value === void 0) {\n value = EVALUATING;\n value = getter();\n }\n return value;\n },\n set(v) {\n Object.defineProperty(object, key, { value: v });\n },\n configurable: true\n });\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\n });\n}\nfunction mergeDefs(...defs) {\n const mergedDescriptors = {};\n for (const def of defs) Object.assign(mergedDescriptors, Object.getOwnPropertyDescriptors(def));\n return Object.defineProperties({}, mergedDescriptors);\n}\nfunction esc(str) {\n return JSON.stringify(str);\n}\nfunction slugify(input) {\n return input.toLowerCase().trim().replace(/[^\\w\\s-]/g, \"\").replace(/[\\s_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nconst allowsEval = cached(() => {\n if (typeof navigator !== \"undefined\" && navigator?.userAgent?.includes(\"Cloudflare\")) return false;\n try {\n new Function(\"\");\n return true;\n } catch (_) {\n return false;\n }\n});\nfunction isPlainObject(o) {\n if (isObject(o) === false) return false;\n const ctor = o.constructor;\n if (ctor === void 0) return true;\n if (typeof ctor !== \"function\") return true;\n const prot = ctor.prototype;\n if (isObject(prot) === false) return false;\n if (Object.prototype.hasOwnProperty.call(prot, \"isPrototypeOf\") === false) return false;\n return true;\n}\nfunction shallowClone(o) {\n if (isPlainObject(o)) return { ...o };\n if (Array.isArray(o)) return [...o];\n return o;\n}\nconst propertyKeyTypes = new Set([\n \"string\",\n \"number\",\n \"symbol\"\n]);\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\nfunction clone(inst, def, params) {\n const cl = new inst._zod.constr(def ?? inst._zod.def);\n if (!def || params?.parent) cl._zod.parent = inst;\n return cl;\n}\nfunction normalizeParams(_params) {\n const params = _params;\n if (!params) return {};\n if (typeof params === \"string\") return { error: () => params };\n if (params?.message !== void 0) {\n if (params?.error !== void 0) throw new Error(\"Cannot specify both `message` and `error` params\");\n params.error = params.message;\n }\n delete params.message;\n if (typeof params.error === \"string\") return {\n ...params,\n error: () => params.error\n };\n return params;\n}\nfunction optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin === \"optional\" && shape[k]._zod.optout === \"optional\";\n });\n}\nconst NUMBER_FORMAT_RANGES = {\n safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],\n int32: [-2147483648, 2147483647],\n uint32: [0, 4294967295],\n float32: [-34028234663852886e22, 34028234663852886e22],\n float64: [-Number.MAX_VALUE, Number.MAX_VALUE]\n};\nfunction pick(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".pick() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = {};\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n newShape[key] = currDef.shape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\n}\nfunction omit(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".omit() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = { ...schema._zod.def.shape };\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n delete newShape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\n}\nfunction extend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to extend: expected a plain object\");\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) {\n const existingShape = schema._zod.def.shape;\n for (const key in shape) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error(\"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.\");\n }\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction safeExtend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to safeExtend: expected a plain object\");\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction merge(a, b) {\n return clone(a, mergeDefs(a._zod.def, {\n get shape() {\n const _shape = {\n ...a._zod.def.shape,\n ...b._zod.def.shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n },\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: []\n }));\n}\nfunction partial(Class, schema, mask) {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(\".partial() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in oldShape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n }\n else for (const key in oldShape) shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n assignProp(this, \"shape\", shape);\n return shape;\n },\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n }\n else for (const key in oldShape) shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n assignProp(this, \"shape\", shape);\n return shape;\n } }));\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction finalizeIssue(iss, ctx, config) {\n const full = {\n ...iss,\n path: iss.path ?? []\n };\n if (!iss.message) full.message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n delete full.inst;\n delete full.continue;\n if (!ctx?.reportInput) delete full.input;\n return full;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\n}\nfunction issue(...args) {\n const [iss, input, inst] = args;\n if (typeof iss === \"string\") return {\n message: iss,\n code: \"custom\",\n input,\n inst\n };\n return { ...iss };\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/errors.js\nconst initializer$1 = (inst, def) => {\n inst.name = \"$ZodError\";\n Object.defineProperty(inst, \"_zod\", {\n value: inst._zod,\n enumerable: false\n });\n Object.defineProperty(inst, \"issues\", {\n value: def,\n enumerable: false\n });\n inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);\n Object.defineProperty(inst, \"toString\", {\n value: () => inst.message,\n enumerable: false\n });\n};\nconst $ZodError = $constructor(\"$ZodError\", initializer$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, { Parent: Error });\nfunction flattenError(error, mapper = (issue) => issue.message) {\n const fieldErrors = {};\n const formErrors = [];\n for (const sub of error.issues) if (sub.path.length > 0) {\n fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];\n fieldErrors[sub.path[0]].push(mapper(sub));\n } else formErrors.push(mapper(sub));\n return {\n formErrors,\n fieldErrors\n };\n}\nfunction formatError(error, mapper = (issue) => issue.message) {\n const fieldErrors = { _errors: [] };\n const processError = (error) => {\n for (const issue of error.issues) if (issue.code === \"invalid_union\" && issue.errors.length) issue.errors.map((issues) => processError({ issues }));\n else if (issue.code === \"invalid_key\") processError({ issues: issue.issues });\n else if (issue.code === \"invalid_element\") processError({ issues: issue.issues });\n else if (issue.path.length === 0) fieldErrors._errors.push(mapper(issue));\n else {\n let curr = fieldErrors;\n let i = 0;\n while (i < issue.path.length) {\n const el = issue.path[i];\n if (!(i === issue.path.length - 1)) curr[el] = curr[el] || { _errors: [] };\n else {\n curr[el] = curr[el] || { _errors: [] };\n curr[el]._errors.push(mapper(issue));\n }\n curr = curr[el];\n i++;\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => (schema, value, _ctx, _params) => {\n const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n if (result.issues.length) {\n const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, _params?.callee);\n throw e;\n }\n return result.value;\n};\nconst _parseAsync = (_Err) => async (schema, value, _ctx, params) => {\n const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n if (result.issues.length) {\n const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, params?.callee);\n throw e;\n }\n return result.value;\n};\nconst _safeParse = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length ? {\n success: false,\n error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);\nconst _safeParseAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n return result.issues.length ? {\n success: false,\n error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);\nconst _encode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? Object.assign(_ctx, { direction: \"backward\" }) : { direction: \"backward\" };\n return _parse(_Err)(schema, value, ctx);\n};\nconst _decode = (_Err) => (schema, value, _ctx) => {\n return _parse(_Err)(schema, value, _ctx);\n};\nconst _encodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? Object.assign(_ctx, { direction: \"backward\" }) : { direction: \"backward\" };\n return _parseAsync(_Err)(schema, value, ctx);\n};\nconst _decodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _parseAsync(_Err)(schema, value, _ctx);\n};\nconst _safeEncode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? Object.assign(_ctx, { direction: \"backward\" }) : { direction: \"backward\" };\n return _safeParse(_Err)(schema, value, ctx);\n};\nconst _safeDecode = (_Err) => (schema, value, _ctx) => {\n return _safeParse(_Err)(schema, value, _ctx);\n};\nconst _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? Object.assign(_ctx, { direction: \"backward\" }) : { direction: \"backward\" };\n return _safeParseAsync(_Err)(schema, value, ctx);\n};\nconst _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _safeParseAsync(_Err)(schema, value, _ctx);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/regexes.js\nconst cuid = /^[cC][^\\s-]{8,}$/;\nconst cuid2 = /^[0-9a-z]+$/;\nconst ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration$1 = /^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$/;\n/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */\nconst guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n/** Returns a regex for validating an RFC 9562/4122 UUID.\n*\n* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */\nconst uuid = (version) => {\n if (!version) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\n return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);\n};\n/** Practical email validation */\nconst email = /^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/;\nconst _emoji$1 = `^(\\\\p{Extended_Pictographic}|\\\\p{Emoji_Component})+$`;\nfunction emoji() {\n return new RegExp(_emoji$1, \"u\");\n}\nconst ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;\nconst ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;\nconst cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$/;\nconst cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;\nconst base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;\nconst base64url = /^[A-Za-z0-9_-]*$/;\nconst e164 = /^\\+[1-9]\\d{6,14}$/;\nconst dateSource = `(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))`;\nconst date$1 = /* @__PURE__ */ new RegExp(`^${dateSource}$`);\nfunction timeSource(args) {\n const hhmm = `(?:[01]\\\\d|2[0-3]):[0-5]\\\\d`;\n return typeof args.precision === \"number\" ? args.precision === -1 ? `${hhmm}` : args.precision === 0 ? `${hhmm}:[0-5]\\\\d` : `${hhmm}:[0-5]\\\\d\\\\.\\\\d{${args.precision}}` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time$1(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime$1(args) {\n const time = timeSource({ precision: args.precision });\n const opts = [\"Z\"];\n if (args.local) opts.push(\"\");\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const timeRegex = `${time}(?:${opts.join(\"|\")})`;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst string$1 = (params) => {\n const regex = params ? `[\\\\s\\\\S]{${params?.minimum ?? 0},${params?.maximum ?? \"\"}}` : `[\\\\s\\\\S]*`;\n return new RegExp(`^${regex}$`);\n};\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /* @__PURE__ */ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst $ZodCheckMaxLength = /* @__PURE__ */ $constructor(\"$ZodCheckMaxLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY;\n if (def.maximum < curr) inst._zod.bag.maximum = def.maximum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length <= def.maximum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_big\",\n maximum: def.maximum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckMinLength = /* @__PURE__ */ $constructor(\"$ZodCheckMinLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY;\n if (def.minimum > curr) inst._zod.bag.minimum = def.minimum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length >= def.minimum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_small\",\n minimum: def.minimum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLengthEquals = /* @__PURE__ */ $constructor(\"$ZodCheckLengthEquals\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.minimum = def.length;\n bag.maximum = def.length;\n bag.length = def.length;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n const length = input.length;\n if (length === def.length) return;\n const origin = getLengthableOrigin(input);\n const tooBig = length > def.length;\n payload.issues.push({\n origin,\n ...tooBig ? {\n code: \"too_big\",\n maximum: def.length\n } : {\n code: \"too_small\",\n minimum: def.length\n },\n inclusive: true,\n exact: true,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStringFormat = /* @__PURE__ */ $constructor(\"$ZodCheckStringFormat\", (inst, def) => {\n var _a, _b;\n $ZodCheck.init(inst, def);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.format = def.format;\n if (def.pattern) {\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(def.pattern);\n }\n });\n if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: def.format,\n input: payload.value,\n ...def.pattern ? { pattern: def.pattern.toString() } : {},\n inst,\n continue: !def.abort\n });\n });\n else (_b = inst._zod).check ?? (_b.check = () => {});\n});\nconst $ZodCheckRegex = /* @__PURE__ */ $constructor(\"$ZodCheckRegex\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"regex\",\n input: payload.value,\n pattern: def.pattern.toString(),\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLowerCase = /* @__PURE__ */ $constructor(\"$ZodCheckLowerCase\", (inst, def) => {\n def.pattern ?? (def.pattern = lowercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckUpperCase = /* @__PURE__ */ $constructor(\"$ZodCheckUpperCase\", (inst, def) => {\n def.pattern ?? (def.pattern = uppercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckIncludes = /* @__PURE__ */ $constructor(\"$ZodCheckIncludes\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const escapedRegex = escapeRegex(def.includes);\n const pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);\n def.pattern = pattern;\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.includes(def.includes, def.position)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"includes\",\n includes: def.includes,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStartsWith = /* @__PURE__ */ $constructor(\"$ZodCheckStartsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.startsWith(def.prefix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"starts_with\",\n prefix: def.prefix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckEndsWith = /* @__PURE__ */ $constructor(\"$ZodCheckEndsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.endsWith(def.suffix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"ends_with\",\n suffix: def.suffix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckOverwrite = /* @__PURE__ */ $constructor(\"$ZodCheckOverwrite\", (inst, def) => {\n $ZodCheck.init(inst, def);\n inst._zod.check = (payload) => {\n payload.value = def.tx(payload.value);\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = []) {\n this.content = [];\n this.indent = 0;\n if (this) this.args = args;\n }\n indented(fn) {\n this.indent += 1;\n fn(this);\n this.indent -= 1;\n }\n write(arg) {\n if (typeof arg === \"function\") {\n arg(this, { execution: \"sync\" });\n arg(this, { execution: \"async\" });\n return;\n }\n const lines = arg.split(\"\\n\").filter((x) => x);\n const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));\n const dedented = lines.map((x) => x.slice(minIndent)).map((x) => \" \".repeat(this.indent * 2) + x);\n for (const line of dedented) this.content.push(line);\n }\n compile() {\n const F = Function;\n const args = this?.args;\n const lines = [...(this?.content ?? [``]).map((x) => ` ${x}`)];\n return new F(...args, lines.join(\"\\n\"));\n }\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 3,\n patch: 6\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js\nconst $ZodType = /* @__PURE__ */ $constructor(\"$ZodType\", (inst, def) => {\n var _a;\n inst ?? (inst = {});\n inst._zod.def = def;\n inst._zod.bag = inst._zod.bag || {};\n inst._zod.version = version;\n const checks = [...inst._zod.def.checks ?? []];\n if (inst._zod.traits.has(\"$ZodCheck\")) checks.unshift(inst);\n for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);\n if (checks.length === 0) {\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred?.push(() => {\n inst._zod.run = inst._zod.parse;\n });\n } else {\n const runChecks = (payload, checks, ctx) => {\n let isAborted = aborted(payload);\n let asyncResult;\n for (const ch of checks) {\n if (ch._zod.def.when) {\n if (!ch._zod.def.when(payload)) continue;\n } else if (isAborted) continue;\n const currLen = payload.issues.length;\n const _ = ch._zod.check(payload);\n if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();\n if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {\n await _;\n if (payload.issues.length === currLen) return;\n if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\n if (!isAborted) isAborted = aborted(payload, currLen);\n }\n }\n if (asyncResult) return asyncResult.then(() => {\n return payload;\n });\n return payload;\n };\n const handleCanaryResult = (canary, payload, ctx) => {\n if (aborted(canary)) {\n canary.aborted = true;\n return canary;\n }\n const checkResult = runChecks(payload, checks, ctx);\n if (checkResult instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));\n }\n return inst._zod.parse(checkResult, ctx);\n };\n inst._zod.run = (payload, ctx) => {\n if (ctx.skipChecks) return inst._zod.parse(payload, ctx);\n if (ctx.direction === \"backward\") {\n const canary = inst._zod.parse({\n value: payload.value,\n issues: []\n }, {\n ...ctx,\n skipChecks: true\n });\n if (canary instanceof Promise) return canary.then((canary) => {\n return handleCanaryResult(canary, payload, ctx);\n });\n return handleCanaryResult(canary, payload, ctx);\n }\n const result = inst._zod.parse(payload, ctx);\n if (result instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return result.then((result) => runChecks(result, checks, ctx));\n }\n return runChecks(result, checks, ctx);\n };\n }\n defineLazy(inst, \"~standard\", () => ({\n validate: (value) => {\n try {\n const r = safeParse$1(inst, value);\n return r.success ? { value: r.data } : { issues: r.error?.issues };\n } catch (_) {\n return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });\n }\n },\n vendor: \"zod\",\n version: 1\n }));\n});\nconst $ZodString = /* @__PURE__ */ $constructor(\"$ZodString\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$1(inst._zod.bag);\n inst._zod.parse = (payload, _) => {\n if (def.coerce) try {\n payload.value = String(payload.value);\n } catch (_) {}\n if (typeof payload.value === \"string\") return payload;\n payload.issues.push({\n expected: \"string\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nconst $ZodStringFormat = /* @__PURE__ */ $constructor(\"$ZodStringFormat\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n $ZodString.init(inst, def);\n});\nconst $ZodGUID = /* @__PURE__ */ $constructor(\"$ZodGUID\", (inst, def) => {\n def.pattern ?? (def.pattern = guid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodUUID = /* @__PURE__ */ $constructor(\"$ZodUUID\", (inst, def) => {\n if (def.version) {\n const v = {\n v1: 1,\n v2: 2,\n v3: 3,\n v4: 4,\n v5: 5,\n v6: 6,\n v7: 7,\n v8: 8\n }[def.version];\n if (v === void 0) throw new Error(`Invalid UUID version: \"${def.version}\"`);\n def.pattern ?? (def.pattern = uuid(v));\n } else def.pattern ?? (def.pattern = uuid());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodEmail = /* @__PURE__ */ $constructor(\"$ZodEmail\", (inst, def) => {\n def.pattern ?? (def.pattern = email);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodURL = /* @__PURE__ */ $constructor(\"$ZodURL\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n try {\n const trimmed = payload.value.trim();\n const url = new URL(trimmed);\n if (def.hostname) {\n def.hostname.lastIndex = 0;\n if (!def.hostname.test(url.hostname)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid hostname\",\n pattern: def.hostname.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n if (def.protocol) {\n def.protocol.lastIndex = 0;\n if (!def.protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.protocol)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid protocol\",\n pattern: def.protocol.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n if (def.normalize) payload.value = url.href;\n else payload.value = trimmed;\n return;\n } catch (_) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodEmoji = /* @__PURE__ */ $constructor(\"$ZodEmoji\", (inst, def) => {\n def.pattern ?? (def.pattern = emoji());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodNanoID = /* @__PURE__ */ $constructor(\"$ZodNanoID\", (inst, def) => {\n def.pattern ?? (def.pattern = nanoid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCUID = /* @__PURE__ */ $constructor(\"$ZodCUID\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCUID2 = /* @__PURE__ */ $constructor(\"$ZodCUID2\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid2);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodULID = /* @__PURE__ */ $constructor(\"$ZodULID\", (inst, def) => {\n def.pattern ?? (def.pattern = ulid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodXID = /* @__PURE__ */ $constructor(\"$ZodXID\", (inst, def) => {\n def.pattern ?? (def.pattern = xid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodKSUID = /* @__PURE__ */ $constructor(\"$ZodKSUID\", (inst, def) => {\n def.pattern ?? (def.pattern = ksuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODateTime = /* @__PURE__ */ $constructor(\"$ZodISODateTime\", (inst, def) => {\n def.pattern ?? (def.pattern = datetime$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /* @__PURE__ */ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /* @__PURE__ */ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /* @__PURE__ */ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodIPv4 = /* @__PURE__ */ $constructor(\"$ZodIPv4\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv4);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv4`;\n});\nconst $ZodIPv6 = /* @__PURE__ */ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv6`;\n inst._zod.check = (payload) => {\n try {\n new URL(`http://[${payload.value}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodCIDRv4 = /* @__PURE__ */ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCIDRv6 = /* @__PURE__ */ $constructor(\"$ZodCIDRv6\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n const parts = payload.value.split(\"/\");\n try {\n if (parts.length !== 2) throw new Error();\n const [address, prefix] = parts;\n if (!prefix) throw new Error();\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) throw new Error();\n if (prefixNum < 0 || prefixNum > 128) throw new Error();\n new URL(`http://[${address}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nfunction isValidBase64(data) {\n if (data === \"\") return true;\n if (data.length % 4 !== 0) return false;\n try {\n atob(data);\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodBase64 = /* @__PURE__ */ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64\";\n inst._zod.check = (payload) => {\n if (isValidBase64(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nfunction isValidBase64URL(data) {\n if (!base64url.test(data)) return false;\n const base64 = data.replace(/[-_]/g, (c) => c === \"-\" ? \"+\" : \"/\");\n return isValidBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\"));\n}\nconst $ZodBase64URL = /* @__PURE__ */ $constructor(\"$ZodBase64URL\", (inst, def) => {\n def.pattern ?? (def.pattern = base64url);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64url\";\n inst._zod.check = (payload) => {\n if (isValidBase64URL(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodE164 = /* @__PURE__ */ $constructor(\"$ZodE164\", (inst, def) => {\n def.pattern ?? (def.pattern = e164);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidJWT(token, algorithm = null) {\n try {\n const tokensParts = token.split(\".\");\n if (tokensParts.length !== 3) return false;\n const [header] = tokensParts;\n if (!header) return false;\n const parsedHeader = JSON.parse(atob(header));\n if (\"typ\" in parsedHeader && parsedHeader?.typ !== \"JWT\") return false;\n if (!parsedHeader.alg) return false;\n if (algorithm && (!(\"alg\" in parsedHeader) || parsedHeader.alg !== algorithm)) return false;\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodJWT = /* @__PURE__ */ $constructor(\"$ZodJWT\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidJWT(payload.value, def.alg)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"jwt\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodUnknown = /* @__PURE__ */ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nconst $ZodNever = /* @__PURE__ */ $constructor(\"$ZodNever\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _ctx) => {\n payload.issues.push({\n expected: \"never\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nfunction handleArrayResult(result, final, index) {\n if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));\n final.value[index] = result.value;\n}\nconst $ZodArray = /* @__PURE__ */ $constructor(\"$ZodArray\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!Array.isArray(input)) {\n payload.issues.push({\n expected: \"array\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = Array(input.length);\n const proms = [];\n for (let i = 0; i < input.length; i++) {\n const item = input[i];\n const result = def.element._zod.run({\n value: item,\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i)));\n else handleArrayResult(result, payload, i);\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handlePropertyResult(result, final, key, input, isOptionalOut) {\n if (result.issues.length) {\n if (isOptionalOut && !(key in input)) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (result.value === void 0) {\n if (key in input) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${k}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n keys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const isOptionalOut = _catchall.optout === \"optional\";\n for (const key in input) {\n if (keySet.has(key)) continue;\n if (t === \"never\") {\n unrecognized.push(key);\n continue;\n }\n const r = _catchall.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalOut);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst\n });\n if (!proms.length) return payload;\n return Promise.all(proms).then(() => {\n return payload;\n });\n}\nconst $ZodObject = /* @__PURE__ */ $constructor(\"$ZodObject\", (inst, def) => {\n $ZodType.init(inst, def);\n if (!Object.getOwnPropertyDescriptor(def, \"shape\")?.get) {\n const sh = def.shape;\n Object.defineProperty(def, \"shape\", { get: () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n return newSh;\n } });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazy(inst._zod, \"propValues\", () => {\n const shape = def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n }\n }\n return propValues;\n });\n const isObject$2 = isObject;\n const catchall = def.catchall;\n let value;\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$2(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = {};\n const proms = [];\n const shape = value.shape;\n for (const key of value.keys) {\n const el = shape[key];\n const isOptionalOut = el._zod.optout === \"optional\";\n const r = el._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalOut);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);\n };\n});\nconst $ZodObjectJIT = /* @__PURE__ */ $constructor(\"$ZodObjectJIT\", (inst, def) => {\n $ZodObject.init(inst, def);\n const superParse = inst._zod.parse;\n const _normalized = cached(() => normalizeDef(def));\n const generateFastpass = (shape) => {\n const doc = new Doc([\n \"shape\",\n \"payload\",\n \"ctx\"\n ]);\n const normalized = _normalized.value;\n const parseStr = (key) => {\n const k = esc(key);\n return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n };\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.keys) ids[key] = `key_${counter++}`;\n doc.write(`const newResult = {};`);\n for (const key of normalized.keys) {\n const id = ids[key];\n const k = esc(key);\n const isOptionalOut = shape[key]?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(key)};`);\n if (isOptionalOut) doc.write(`\n if (${id}.issues.length) {\n if (${k} in input) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n else doc.write(`\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n const fn = doc.compile();\n return (payload, ctx) => fn(shape, payload, ctx);\n };\n let fastpass;\n const isObject$1 = isObject;\n const jit = !globalConfig.jitless;\n const allowsEval$1 = allowsEval;\n const fastEnabled = jit && allowsEval$1.value;\n const catchall = def.catchall;\n let value;\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$1(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n if (jit && fastEnabled && ctx?.async === false && ctx.jitless !== true) {\n if (!fastpass) fastpass = generateFastpass(def.shape);\n payload = fastpass(payload, ctx);\n if (!catchall) return payload;\n return handleCatchall([], input, payload, ctx, value, inst);\n }\n return superParse(payload, ctx);\n };\n});\nfunction handleUnionResults(results, final, inst, ctx) {\n for (const result of results) if (result.issues.length === 0) {\n final.value = result.value;\n return final;\n }\n const nonaborted = results.filter((r) => !aborted(r));\n if (nonaborted.length === 1) {\n final.value = nonaborted[0].value;\n return nonaborted[0];\n }\n final.issues.push({\n code: \"invalid_union\",\n input: final.value,\n inst,\n errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n });\n return final;\n}\nconst $ZodUnion = /* @__PURE__ */ $constructor(\"$ZodUnion\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.options.some((o) => o._zod.optin === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"optout\", () => def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"values\", () => {\n if (def.options.every((o) => o._zod.values)) return new Set(def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazy(inst._zod, \"pattern\", () => {\n if (def.options.every((o) => o._zod.pattern)) {\n const patterns = def.options.map((o) => o._zod.pattern);\n return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join(\"|\")})$`);\n }\n });\n const single = def.options.length === 1;\n const first = def.options[0]._zod.run;\n inst._zod.parse = (payload, ctx) => {\n if (single) return first(payload, ctx);\n let async = false;\n const results = [];\n for (const option of def.options) {\n const result = option._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) {\n results.push(result);\n async = true;\n } else {\n if (result.issues.length === 0) return result;\n results.push(result);\n }\n }\n if (!async) return handleUnionResults(results, payload, inst, ctx);\n return Promise.all(results).then((results) => {\n return handleUnionResults(results, payload, inst, ctx);\n });\n };\n});\nconst $ZodIntersection = /* @__PURE__ */ $constructor(\"$ZodIntersection\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n const left = def.left._zod.run({\n value: input,\n issues: []\n }, ctx);\n const right = def.right._zod.run({\n value: input,\n issues: []\n }, ctx);\n if (left instanceof Promise || right instanceof Promise) return Promise.all([left, right]).then(([left, right]) => {\n return handleIntersectionResults(payload, left, right);\n });\n return handleIntersectionResults(payload, left, right);\n };\n});\nfunction mergeValues(a, b) {\n if (a === b) return {\n valid: true,\n data: a\n };\n if (a instanceof Date && b instanceof Date && +a === +b) return {\n valid: true,\n data: a\n };\n if (isPlainObject(a) && isPlainObject(b)) {\n const bKeys = Object.keys(b);\n const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);\n const newObj = {\n ...a,\n ...b\n };\n for (const key of sharedKeys) {\n const sharedValue = mergeValues(a[key], b[key]);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [key, ...sharedValue.mergeErrorPath]\n };\n newObj[key] = sharedValue.data;\n }\n return {\n valid: true,\n data: newObj\n };\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return {\n valid: false,\n mergeErrorPath: []\n };\n const newArray = [];\n for (let index = 0; index < a.length; index++) {\n const itemA = a[index];\n const itemB = b[index];\n const sharedValue = mergeValues(itemA, itemB);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [index, ...sharedValue.mergeErrorPath]\n };\n newArray.push(sharedValue.data);\n }\n return {\n valid: true,\n data: newArray\n };\n }\n return {\n valid: false,\n mergeErrorPath: []\n };\n}\nfunction handleIntersectionResults(result, left, right) {\n const unrecKeys = /* @__PURE__ */ new Map();\n let unrecIssue;\n for (const iss of left.issues) if (iss.code === \"unrecognized_keys\") {\n unrecIssue ?? (unrecIssue = iss);\n for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).l = true;\n }\n } else result.issues.push(iss);\n for (const iss of right.issues) if (iss.code === \"unrecognized_keys\") for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).r = true;\n }\n else result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length && unrecIssue) result.issues.push({\n ...unrecIssue,\n keys: bothKeys\n });\n if (aborted(result)) return result;\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n result.value = merged.data;\n return result;\n}\nconst $ZodRecord = /* @__PURE__ */ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!isPlainObject(input)) {\n payload.issues.push({\n expected: \"record\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n const proms = [];\n const values = def.keyType._zod.values;\n if (values) {\n payload.value = {};\n const recordKeys = /* @__PURE__ */ new Set();\n for (const key of values) if (typeof key === \"string\" || typeof key === \"number\" || typeof key === \"symbol\") {\n recordKeys.add(typeof key === \"number\" ? key.toString() : key);\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[key] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[key] = result.value;\n }\n }\n let unrecognized;\n for (const key in input) if (!recordKeys.has(key)) {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized\n });\n } else {\n payload.value = {};\n for (const key of Reflect.ownKeys(input)) {\n if (key === \"__proto__\") continue;\n let keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (typeof key === \"string\" && number.test(key) && keyResult.issues.length) {\n const retryResult = def.keyType._zod.run({\n value: Number(key),\n issues: []\n }, ctx);\n if (retryResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (retryResult.issues.length === 0) keyResult = retryResult;\n }\n if (keyResult.issues.length) {\n if (def.mode === \"loose\") payload.value[key] = input[key];\n else payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }\n }\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nconst $ZodEnum = /* @__PURE__ */ $constructor(\"$ZodEnum\", (inst, def) => {\n $ZodType.init(inst, def);\n const values = getEnumValues(def.entries);\n const valuesSet = new Set(values);\n inst._zod.values = valuesSet;\n inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === \"string\" ? escapeRegex(o) : o.toString()).join(\"|\")})$`);\n inst._zod.parse = (payload, _ctx) => {\n const input = payload.value;\n if (valuesSet.has(input)) return payload;\n payload.issues.push({\n code: \"invalid_value\",\n values,\n input,\n inst\n });\n return payload;\n };\n});\nconst $ZodTransform = /* @__PURE__ */ $constructor(\"$ZodTransform\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n const _out = def.transform(payload.value, payload);\n if (ctx.async) return (_out instanceof Promise ? _out : Promise.resolve(_out)).then((output) => {\n payload.value = output;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\n return payload;\n };\n});\nfunction handleOptionalResult(result, input) {\n if (result.issues.length && input === void 0) return {\n issues: [],\n value: void 0\n };\n return result;\n}\nconst $ZodOptional = /* @__PURE__ */ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n inst._zod.optout = \"optional\";\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;\n });\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (def.innerType._zod.optin === \"optional\") {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, payload.value));\n return handleOptionalResult(result, payload.value);\n }\n if (payload.value === void 0) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodExactOptional = /* @__PURE__ */ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"pattern\", () => def.innerType._zod.pattern);\n inst._zod.parse = (payload, ctx) => {\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNullable = /* @__PURE__ */ $constructor(\"$ZodNullable\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === null) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodDefault = /* @__PURE__ */ $constructor(\"$ZodDefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) {\n payload.value = def.defaultValue;\n /**\n * $ZodDefault returns the default value immediately in forward direction.\n * It doesn't pass the default value into the validator (\"prefault\"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a \"prefault\" for the pipe. */\n return payload;\n }\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleDefaultResult(result, def));\n return handleDefaultResult(result, def);\n };\n});\nfunction handleDefaultResult(payload, def) {\n if (payload.value === void 0) payload.value = def.defaultValue;\n return payload;\n}\nconst $ZodPrefault = /* @__PURE__ */ $constructor(\"$ZodPrefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) payload.value = def.defaultValue;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNonOptional = /* @__PURE__ */ $constructor(\"$ZodNonOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => {\n const v = def.innerType._zod.values;\n return v ? new Set([...v].filter((x) => x !== void 0)) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleNonOptionalResult(result, inst));\n return handleNonOptionalResult(result, inst);\n };\n});\nfunction handleNonOptionalResult(payload, inst) {\n if (!payload.issues.length && payload.value === void 0) payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: payload.value,\n inst\n });\n return payload;\n}\nconst $ZodCatch = /* @__PURE__ */ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => {\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n }\n return payload;\n });\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n }\n return payload;\n };\n});\nconst $ZodPipe = /* @__PURE__ */ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.in._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.in._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.out._zod.optout);\n defineLazy(inst._zod, \"propValues\", () => def.in._zod.propValues);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") {\n const right = def.out._zod.run(payload, ctx);\n if (right instanceof Promise) return right.then((right) => handlePipeResult(right, def.in, ctx));\n return handlePipeResult(right, def.in, ctx);\n }\n const left = def.in._zod.run(payload, ctx);\n if (left instanceof Promise) return left.then((left) => handlePipeResult(left, def.out, ctx));\n return handlePipeResult(left, def.out, ctx);\n };\n});\nfunction handlePipeResult(left, next, ctx) {\n if (left.issues.length) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues\n }, ctx);\n}\nconst $ZodReadonly = /* @__PURE__ */ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"propValues\", () => def.innerType._zod.propValues);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.innerType?._zod?.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType?._zod?.optout);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then(handleReadonlyResult);\n return handleReadonlyResult(result);\n };\n});\nfunction handleReadonlyResult(payload) {\n payload.value = Object.freeze(payload.value);\n return payload;\n}\nconst $ZodCustom = /* @__PURE__ */ $constructor(\"$ZodCustom\", (inst, def) => {\n $ZodCheck.init(inst, def);\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _) => {\n return payload;\n };\n inst._zod.check = (payload) => {\n const input = payload.value;\n const r = def.fn(input);\n if (r instanceof Promise) return r.then((r) => handleRefineResult(r, payload, input, inst));\n handleRefineResult(r, payload, input, inst);\n };\n});\nfunction handleRefineResult(result, payload, input, inst) {\n if (!result) {\n const _iss = {\n code: \"custom\",\n input,\n inst,\n path: [...inst._zod.def.path ?? []],\n continue: !inst._zod.def.abort\n };\n if (inst._zod.def.params) _iss.params = inst._zod.def.params;\n payload.issues.push(issue(_iss));\n }\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/registries.js\nvar _a;\nvar $ZodRegistry = class {\n constructor() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n }\n add(schema, ..._meta) {\n const meta = _meta[0];\n this._map.set(schema, meta);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.set(meta.id, schema);\n return this;\n }\n clear() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n return this;\n }\n remove(schema) {\n const meta = this._map.get(schema);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.delete(meta.id);\n this._map.delete(schema);\n return this;\n }\n get(schema) {\n const p = schema._zod.parent;\n if (p) {\n const pm = { ...this.get(p) ?? {} };\n delete pm.id;\n const f = {\n ...pm,\n ...this._map.get(schema)\n };\n return Object.keys(f).length ? f : void 0;\n }\n return this._map.get(schema);\n }\n has(schema) {\n return this._map.has(schema);\n }\n};\nfunction registry() {\n return new $ZodRegistry();\n}\n(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());\nconst globalRegistry = globalThis.__zod_globalRegistry;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/api.js\n/* @__NO_SIDE_EFFECTS__ */\nfunction _string(Class, params) {\n return new Class({\n type: \"string\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _email(Class, params) {\n return new Class({\n type: \"string\",\n format: \"email\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _guid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"guid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v4\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v6\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv7(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v7\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _emoji(Class, params) {\n return new Class({\n type: \"string\",\n format: \"emoji\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _nanoid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"nanoid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cuid2(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid2\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ulid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ulid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _xid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"xid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ksuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ksuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ipv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ipv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cidrv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cidrv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _base64(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _base64url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _e164(Class, params) {\n return new Class({\n type: \"string\",\n format: \"e164\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _jwt(Class, params) {\n return new Class({\n type: \"string\",\n format: \"jwt\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDateTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"datetime\",\n check: \"string_format\",\n offset: false,\n local: false,\n precision: null,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDate(Class, params) {\n return new Class({\n type: \"string\",\n format: \"date\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"time\",\n check: \"string_format\",\n precision: null,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDuration(Class, params) {\n return new Class({\n type: \"string\",\n format: \"duration\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _never(Class, params) {\n return new Class({\n type: \"never\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _maxLength(maximum, params) {\n return new $ZodCheckMaxLength({\n check: \"max_length\",\n ...normalizeParams(params),\n maximum\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _minLength(minimum, params) {\n return new $ZodCheckMinLength({\n check: \"min_length\",\n ...normalizeParams(params),\n minimum\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _length(length, params) {\n return new $ZodCheckLengthEquals({\n check: \"length_equals\",\n ...normalizeParams(params),\n length\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _regex(pattern, params) {\n return new $ZodCheckRegex({\n check: \"string_format\",\n format: \"regex\",\n ...normalizeParams(params),\n pattern\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _lowercase(params) {\n return new $ZodCheckLowerCase({\n check: \"string_format\",\n format: \"lowercase\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uppercase(params) {\n return new $ZodCheckUpperCase({\n check: \"string_format\",\n format: \"uppercase\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _includes(includes, params) {\n return new $ZodCheckIncludes({\n check: \"string_format\",\n format: \"includes\",\n ...normalizeParams(params),\n includes\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _startsWith(prefix, params) {\n return new $ZodCheckStartsWith({\n check: \"string_format\",\n format: \"starts_with\",\n ...normalizeParams(params),\n prefix\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _endsWith(suffix, params) {\n return new $ZodCheckEndsWith({\n check: \"string_format\",\n format: \"ends_with\",\n ...normalizeParams(params),\n suffix\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _overwrite(tx) {\n return new $ZodCheckOverwrite({\n check: \"overwrite\",\n tx\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _normalize(form) {\n return /* @__PURE__ */ _overwrite((input) => input.normalize(form));\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _trim() {\n return /* @__PURE__ */ _overwrite((input) => input.trim());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _toLowerCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toLowerCase());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _toUpperCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toUpperCase());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _slugify() {\n return /* @__PURE__ */ _overwrite((input) => slugify(input));\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _array(Class, element, params) {\n return new Class({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _refine(Class, fn, _params) {\n return new Class({\n type: \"custom\",\n check: \"custom\",\n fn,\n ...normalizeParams(_params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _superRefine(fn) {\n const ch = /* @__PURE__ */ _check((payload) => {\n payload.addIssue = (issue$2) => {\n if (typeof issue$2 === \"string\") payload.issues.push(issue(issue$2, payload.value, ch._zod.def));\n else {\n const _issue = issue$2;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n _issue.input ?? (_issue.input = payload.value);\n _issue.inst ?? (_issue.inst = ch);\n _issue.continue ?? (_issue.continue = !ch._zod.def.abort);\n payload.issues.push(issue(_issue));\n }\n };\n return fn(payload.value, payload);\n });\n return ch;\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _check(fn, params) {\n const ch = new $ZodCheck({\n check: \"custom\",\n ...normalizeParams(params)\n });\n ch._zod.check = fn;\n return ch;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/to-json-schema.js\nfunction initializeContext(params) {\n let target = params?.target ?? \"draft-2020-12\";\n if (target === \"draft-4\") target = \"draft-04\";\n if (target === \"draft-7\") target = \"draft-07\";\n return {\n processors: params.processors ?? {},\n metadataRegistry: params?.metadata ?? globalRegistry,\n target,\n unrepresentable: params?.unrepresentable ?? \"throw\",\n override: params?.override ?? (() => {}),\n io: params?.io ?? \"output\",\n counter: 0,\n seen: /* @__PURE__ */ new Map(),\n cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n external: params?.external ?? void 0\n };\n}\nfunction process$1(schema, ctx, _params = {\n path: [],\n schemaPath: []\n}) {\n var _a;\n const def = schema._zod.def;\n const seen = ctx.seen.get(schema);\n if (seen) {\n seen.count++;\n if (_params.schemaPath.includes(schema)) seen.cycle = _params.path;\n return seen.schema;\n }\n const result = {\n schema: {},\n count: 1,\n cycle: void 0,\n path: _params.path\n };\n ctx.seen.set(schema, result);\n const overrideSchema = schema._zod.toJSONSchema?.();\n if (overrideSchema) result.schema = overrideSchema;\n else {\n const params = {\n ..._params,\n schemaPath: [..._params.schemaPath, schema],\n path: _params.path\n };\n if (schema._zod.processJSONSchema) schema._zod.processJSONSchema(ctx, result.schema, params);\n else {\n const _json = result.schema;\n const processor = ctx.processors[def.type];\n if (!processor) throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);\n processor(schema, ctx, _json, params);\n }\n const parent = schema._zod.parent;\n if (parent) {\n if (!result.ref) result.ref = parent;\n process$1(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) Object.assign(result.schema, meta);\n if (ctx.io === \"input\" && isTransforming(schema)) {\n delete result.schema.examples;\n delete result.schema.default;\n }\n if (ctx.io === \"input\" && result.schema._prefault) (_a = result.schema).default ?? (_a.default = result.schema._prefault);\n delete result.schema._prefault;\n return ctx.seen.get(schema).schema;\n}\nfunction extractDefs(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const idToSchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.entries()) {\n const id = ctx.metadataRegistry.get(entry[0])?.id;\n if (id) {\n const existing = idToSchema.get(id);\n if (existing && existing !== entry[0]) throw new Error(`Duplicate schema id \"${id}\" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);\n idToSchema.set(id, entry[0]);\n }\n }\n const makeURI = (entry) => {\n const defsSegment = ctx.target === \"draft-2020-12\" ? \"$defs\" : \"definitions\";\n if (ctx.external) {\n const externalId = ctx.external.registry.get(entry[0])?.id;\n const uriGenerator = ctx.external.uri ?? ((id) => id);\n if (externalId) return { ref: uriGenerator(externalId) };\n const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;\n entry[1].defId = id;\n return {\n defId: id,\n ref: `${uriGenerator(\"__shared\")}#/${defsSegment}/${id}`\n };\n }\n if (entry[1] === root) return { ref: \"#\" };\n const defUriPrefix = `#/${defsSegment}/`;\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + defId\n };\n };\n const extractToDef = (entry) => {\n if (entry[1].schema.$ref) return;\n const seen = entry[1];\n const { ref, defId } = makeURI(entry);\n seen.def = { ...seen.schema };\n if (defId) seen.defId = defId;\n const schema = seen.schema;\n for (const key in schema) delete schema[key];\n schema.$ref = ref;\n };\n if (ctx.cycles === \"throw\") for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.cycle) throw new Error(`Cycle detected: #/${seen.cycle?.join(\"/\")}/<root>\n\nSet the \\`cycles\\` parameter to \\`\"ref\"\\` to resolve cyclical schemas with defs.`);\n }\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (schema === entry[0]) {\n extractToDef(entry);\n continue;\n }\n if (ctx.external) {\n const ext = ctx.external.registry.get(entry[0])?.id;\n if (schema !== entry[0] && ext) {\n extractToDef(entry);\n continue;\n }\n }\n if (ctx.metadataRegistry.get(entry[0])?.id) {\n extractToDef(entry);\n continue;\n }\n if (seen.cycle) {\n extractToDef(entry);\n continue;\n }\n if (seen.count > 1) {\n if (ctx.reused === \"ref\") {\n extractToDef(entry);\n continue;\n }\n }\n }\n}\nfunction finalize(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const flattenRef = (zodSchema) => {\n const seen = ctx.seen.get(zodSchema);\n if (seen.ref === null) return;\n const schema = seen.def ?? seen.schema;\n const _cached = { ...schema };\n const ref = seen.ref;\n seen.ref = null;\n if (ref) {\n flattenRef(ref);\n const refSeen = ctx.seen.get(ref);\n const refSchema = refSeen.schema;\n if (refSchema.$ref && (ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\")) {\n schema.allOf = schema.allOf ?? [];\n schema.allOf.push(refSchema);\n } else Object.assign(schema, refSchema);\n Object.assign(schema, _cached);\n if (zodSchema._zod.parent === ref) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (!(key in _cached)) delete schema[key];\n }\n if (refSchema.$ref && refSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) delete schema[key];\n }\n }\n const parent = zodSchema._zod.parent;\n if (parent && parent !== ref) {\n flattenRef(parent);\n const parentSeen = ctx.seen.get(parent);\n if (parentSeen?.schema.$ref) {\n schema.$ref = parentSeen.schema.$ref;\n if (parentSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) delete schema[key];\n }\n }\n }\n ctx.override({\n zodSchema,\n jsonSchema: schema,\n path: seen.path ?? []\n });\n };\n for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\n const result = {};\n if (ctx.target === \"draft-2020-12\") result.$schema = \"https://json-schema.org/draft/2020-12/schema\";\n else if (ctx.target === \"draft-07\") result.$schema = \"http://json-schema.org/draft-07/schema#\";\n else if (ctx.target === \"draft-04\") result.$schema = \"http://json-schema.org/draft-04/schema#\";\n else if (ctx.target === \"openapi-3.0\") {}\n if (ctx.external?.uri) {\n const id = ctx.external.registry.get(schema)?.id;\n if (!id) throw new Error(\"Schema is missing an `id` property\");\n result.$id = ctx.external.uri(id);\n }\n Object.assign(result, root.def ?? root.schema);\n const defs = ctx.external?.defs ?? {};\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.def && seen.defId) defs[seen.defId] = seen.def;\n }\n if (ctx.external) {} else if (Object.keys(defs).length > 0) if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\n try {\n const finalized = JSON.parse(JSON.stringify(result));\n Object.defineProperty(finalized, \"~standard\", {\n value: {\n ...schema[\"~standard\"],\n jsonSchema: {\n input: createStandardJSONSchemaMethod(schema, \"input\", ctx.processors),\n output: createStandardJSONSchemaMethod(schema, \"output\", ctx.processors)\n }\n },\n enumerable: false,\n writable: false\n });\n return finalized;\n } catch (_err) {\n throw new Error(\"Error converting schema to JSON.\");\n }\n}\nfunction isTransforming(_schema, _ctx) {\n const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() };\n if (ctx.seen.has(_schema)) return false;\n ctx.seen.add(_schema);\n const def = _schema._zod.def;\n if (def.type === \"transform\") return true;\n if (def.type === \"array\") return isTransforming(def.element, ctx);\n if (def.type === \"set\") return isTransforming(def.valueType, ctx);\n if (def.type === \"lazy\") return isTransforming(def.getter(), ctx);\n if (def.type === \"promise\" || def.type === \"optional\" || def.type === \"nonoptional\" || def.type === \"nullable\" || def.type === \"readonly\" || def.type === \"default\" || def.type === \"prefault\") return isTransforming(def.innerType, ctx);\n if (def.type === \"intersection\") return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);\n if (def.type === \"record\" || def.type === \"map\") return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);\n if (def.type === \"pipe\") return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);\n if (def.type === \"object\") {\n for (const key in def.shape) if (isTransforming(def.shape[key], ctx)) return true;\n return false;\n }\n if (def.type === \"union\") {\n for (const option of def.options) if (isTransforming(option, ctx)) return true;\n return false;\n }\n if (def.type === \"tuple\") {\n for (const item of def.items) if (isTransforming(item, ctx)) return true;\n if (def.rest && isTransforming(def.rest, ctx)) return true;\n return false;\n }\n return false;\n}\n/**\n* Creates a toJSONSchema method for a schema instance.\n* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing.\n*/\nconst createToJSONSchemaMethod = (schema, processors = {}) => (params) => {\n const ctx = initializeContext({\n ...params,\n processors\n });\n process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\nconst createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {\n const { libraryOptions, target } = params ?? {};\n const ctx = initializeContext({\n ...libraryOptions ?? {},\n target,\n io,\n processors\n });\n process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/json-schema-processors.js\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minLength = minimum;\n if (typeof maximum === \"number\") json.maxLength = maximum;\n if (format) {\n json.format = formatMap[format] ?? format;\n if (json.format === \"\") delete json.format;\n if (format === \"time\") delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const regexes = [...patterns];\n if (regexes.length === 1) json.pattern = regexes[0].source;\n else if (regexes.length > 1) json.allOf = [...regexes.map((regex) => ({\n ...ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\" ? { type: \"string\" } : {},\n pattern: regex.source\n }))];\n }\n};\nconst neverProcessor = (_schema, _ctx, json, _params) => {\n json.not = {};\n};\nconst unknownProcessor = (_schema, _ctx, _json, _params) => {};\nconst enumProcessor = (schema, _ctx, json, _params) => {\n const def = schema._zod.def;\n const values = getEnumValues(def.entries);\n if (values.every((v) => typeof v === \"number\")) json.type = \"number\";\n if (values.every((v) => typeof v === \"string\")) json.type = \"string\";\n json.enum = values;\n};\nconst customProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"Transforms cannot be represented in JSON Schema\");\n};\nconst arrayProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const { minimum, maximum } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = process$1(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n json.properties = {};\n const shape = def.shape;\n for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n });\n const allKeys = new Set(Object.keys(shape));\n const requiredKeys = new Set([...allKeys].filter((key) => {\n const v = def.shape[key]._zod;\n if (ctx.io === \"input\") return v.optin === void 0;\n else return v.optout === void 0;\n }));\n if (requiredKeys.size > 0) json.required = Array.from(requiredKeys);\n if (def.catchall?._zod.def.type === \"never\") json.additionalProperties = false;\n else if (!def.catchall) {\n if (ctx.io === \"output\") json.additionalProperties = false;\n } else if (def.catchall) json.additionalProperties = process$1(def.catchall, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n};\nconst unionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const isExclusive = def.inclusive === false;\n const options = def.options.map((x, i) => process$1(x, ctx, {\n ...params,\n path: [\n ...params.path,\n isExclusive ? \"oneOf\" : \"anyOf\",\n i\n ]\n }));\n if (isExclusive) json.oneOf = options;\n else json.anyOf = options;\n};\nconst intersectionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const a = process$1(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = process$1(def.right, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 1\n ]\n });\n const isSimpleIntersection = (val) => \"allOf\" in val && Object.keys(val).length === 1;\n json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\n};\nconst recordProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n const keyType = def.keyType;\n const patterns = keyType._zod.bag?.patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = process$1(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") json.propertyNames = process$1(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n json.additionalProperties = process$1(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n if (keyValues) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues;\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n if (ctx.target === \"openapi-3.0\") {\n seen.ref = def.innerType;\n json.nullable = true;\n } else json.anyOf = [inner, { type: \"null\" }];\n};\nconst nonoptionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.default = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io === \"input\") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n let catchValue;\n try {\n catchValue = def.catchValue(void 0);\n } catch {\n throw new Error(\"Dynamic catch values are not supported in JSON Schema\");\n }\n json.default = catchValue;\n};\nconst pipeProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n const innerType = ctx.io === \"input\" ? def.in._zod.def.type === \"transform\" ? def.out : def.in : def.out;\n process$1(innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = innerType;\n};\nconst readonlyProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.readOnly = true;\n};\nconst optionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/iso.js\nconst ZodISODateTime = /* @__PURE__ */ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction datetime(params) {\n return _isoDateTime(ZodISODateTime, params);\n}\nconst ZodISODate = /* @__PURE__ */ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction date(params) {\n return _isoDate(ZodISODate, params);\n}\nconst ZodISOTime = /* @__PURE__ */ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction time(params) {\n return _isoTime(ZodISOTime, params);\n}\nconst ZodISODuration = /* @__PURE__ */ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction duration(params) {\n return _isoDuration(ZodISODuration, params);\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/errors.js\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n Object.defineProperties(inst, {\n format: { value: (mapper) => formatError(inst, mapper) },\n flatten: { value: (mapper) => flattenError(inst, mapper) },\n addIssue: { value: (issue) => {\n inst.issues.push(issue);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n addIssues: { value: (issues) => {\n inst.issues.push(...issues);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n isEmpty: { get() {\n return inst.issues.length === 0;\n } }\n });\n};\nconst ZodError = $constructor(\"ZodError\", initializer);\nconst ZodRealError = $constructor(\"ZodError\", initializer, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/parse.js\nconst parse = /* @__PURE__ */ _parse(ZodRealError);\nconst parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);\nconst safeParse = /* @__PURE__ */ _safeParse(ZodRealError);\nconst safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);\nconst encode = /* @__PURE__ */ _encode(ZodRealError);\nconst decode = /* @__PURE__ */ _decode(ZodRealError);\nconst encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);\nconst decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);\nconst safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);\nconst safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);\nconst safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);\nconst safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/schemas.js\nconst ZodType = /* @__PURE__ */ $constructor(\"ZodType\", (inst, def) => {\n $ZodType.init(inst, def);\n Object.assign(inst[\"~standard\"], { jsonSchema: {\n input: createStandardJSONSchemaMethod(inst, \"input\"),\n output: createStandardJSONSchemaMethod(inst, \"output\")\n } });\n inst.toJSONSchema = createToJSONSchemaMethod(inst, {});\n inst.def = def;\n inst.type = def.type;\n Object.defineProperty(inst, \"_def\", { value: def });\n inst.check = (...checks) => {\n return inst.clone(mergeDefs(def, { checks: [...def.checks ?? [], ...checks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)] }), { parent: true });\n };\n inst.with = inst.check;\n inst.clone = (def, params) => clone(inst, def, params);\n inst.brand = () => inst;\n inst.register = ((reg, meta) => {\n reg.add(inst, meta);\n return inst;\n });\n inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });\n inst.safeParse = (data, params) => safeParse(inst, data, params);\n inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });\n inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);\n inst.spa = inst.safeParseAsync;\n inst.encode = (data, params) => encode(inst, data, params);\n inst.decode = (data, params) => decode(inst, data, params);\n inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);\n inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);\n inst.safeEncode = (data, params) => safeEncode(inst, data, params);\n inst.safeDecode = (data, params) => safeDecode(inst, data, params);\n inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);\n inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);\n inst.refine = (check, params) => inst.check(refine(check, params));\n inst.superRefine = (refinement) => inst.check(superRefine(refinement));\n inst.overwrite = (fn) => inst.check(_overwrite(fn));\n inst.optional = () => optional(inst);\n inst.exactOptional = () => exactOptional(inst);\n inst.nullable = () => nullable(inst);\n inst.nullish = () => optional(nullable(inst));\n inst.nonoptional = (params) => nonoptional(inst, params);\n inst.array = () => array(inst);\n inst.or = (arg) => union([inst, arg]);\n inst.and = (arg) => intersection(inst, arg);\n inst.transform = (tx) => pipe(inst, transform(tx));\n inst.default = (def) => _default(inst, def);\n inst.prefault = (def) => prefault(inst, def);\n inst.catch = (params) => _catch(inst, params);\n inst.pipe = (target) => pipe(inst, target);\n inst.readonly = () => readonly(inst);\n inst.describe = (description) => {\n const cl = inst.clone();\n globalRegistry.add(cl, { description });\n return cl;\n };\n Object.defineProperty(inst, \"description\", {\n get() {\n return globalRegistry.get(inst)?.description;\n },\n configurable: true\n });\n inst.meta = (...args) => {\n if (args.length === 0) return globalRegistry.get(inst);\n const cl = inst.clone();\n globalRegistry.add(cl, args[0]);\n return cl;\n };\n inst.isOptional = () => inst.safeParse(void 0).success;\n inst.isNullable = () => inst.safeParse(null).success;\n inst.apply = (fn) => fn(inst);\n return inst;\n});\n/** @internal */\nconst _ZodString = /* @__PURE__ */ $constructor(\"_ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => stringProcessor(inst, ctx, json, params);\n const bag = inst._zod.bag;\n inst.format = bag.format ?? null;\n inst.minLength = bag.minimum ?? null;\n inst.maxLength = bag.maximum ?? null;\n inst.regex = (...args) => inst.check(_regex(...args));\n inst.includes = (...args) => inst.check(_includes(...args));\n inst.startsWith = (...args) => inst.check(_startsWith(...args));\n inst.endsWith = (...args) => inst.check(_endsWith(...args));\n inst.min = (...args) => inst.check(_minLength(...args));\n inst.max = (...args) => inst.check(_maxLength(...args));\n inst.length = (...args) => inst.check(_length(...args));\n inst.nonempty = (...args) => inst.check(_minLength(1, ...args));\n inst.lowercase = (params) => inst.check(_lowercase(params));\n inst.uppercase = (params) => inst.check(_uppercase(params));\n inst.trim = () => inst.check(_trim());\n inst.normalize = (...args) => inst.check(_normalize(...args));\n inst.toLowerCase = () => inst.check(_toLowerCase());\n inst.toUpperCase = () => inst.check(_toUpperCase());\n inst.slugify = () => inst.check(_slugify());\n});\nconst ZodString = /* @__PURE__ */ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n inst.email = (params) => inst.check(_email(ZodEmail, params));\n inst.url = (params) => inst.check(_url(ZodURL, params));\n inst.jwt = (params) => inst.check(_jwt(ZodJWT, params));\n inst.emoji = (params) => inst.check(_emoji(ZodEmoji, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.uuid = (params) => inst.check(_uuid(ZodUUID, params));\n inst.uuidv4 = (params) => inst.check(_uuidv4(ZodUUID, params));\n inst.uuidv6 = (params) => inst.check(_uuidv6(ZodUUID, params));\n inst.uuidv7 = (params) => inst.check(_uuidv7(ZodUUID, params));\n inst.nanoid = (params) => inst.check(_nanoid(ZodNanoID, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.cuid = (params) => inst.check(_cuid(ZodCUID, params));\n inst.cuid2 = (params) => inst.check(_cuid2(ZodCUID2, params));\n inst.ulid = (params) => inst.check(_ulid(ZodULID, params));\n inst.base64 = (params) => inst.check(_base64(ZodBase64, params));\n inst.base64url = (params) => inst.check(_base64url(ZodBase64URL, params));\n inst.xid = (params) => inst.check(_xid(ZodXID, params));\n inst.ksuid = (params) => inst.check(_ksuid(ZodKSUID, params));\n inst.ipv4 = (params) => inst.check(_ipv4(ZodIPv4, params));\n inst.ipv6 = (params) => inst.check(_ipv6(ZodIPv6, params));\n inst.cidrv4 = (params) => inst.check(_cidrv4(ZodCIDRv4, params));\n inst.cidrv6 = (params) => inst.check(_cidrv6(ZodCIDRv6, params));\n inst.e164 = (params) => inst.check(_e164(ZodE164, params));\n inst.datetime = (params) => inst.check(datetime(params));\n inst.date = (params) => inst.check(date(params));\n inst.time = (params) => inst.check(time(params));\n inst.duration = (params) => inst.check(duration(params));\n});\nfunction string(params) {\n return _string(ZodString, params);\n}\nconst ZodStringFormat = /* @__PURE__ */ $constructor(\"ZodStringFormat\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n _ZodString.init(inst, def);\n});\nconst ZodEmail = /* @__PURE__ */ $constructor(\"ZodEmail\", (inst, def) => {\n $ZodEmail.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodGUID = /* @__PURE__ */ $constructor(\"ZodGUID\", (inst, def) => {\n $ZodGUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUUID = /* @__PURE__ */ $constructor(\"ZodUUID\", (inst, def) => {\n $ZodUUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodURL = /* @__PURE__ */ $constructor(\"ZodURL\", (inst, def) => {\n $ZodURL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmoji = /* @__PURE__ */ $constructor(\"ZodEmoji\", (inst, def) => {\n $ZodEmoji.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodNanoID = /* @__PURE__ */ $constructor(\"ZodNanoID\", (inst, def) => {\n $ZodNanoID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCUID = /* @__PURE__ */ $constructor(\"ZodCUID\", (inst, def) => {\n $ZodCUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCUID2 = /* @__PURE__ */ $constructor(\"ZodCUID2\", (inst, def) => {\n $ZodCUID2.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodULID = /* @__PURE__ */ $constructor(\"ZodULID\", (inst, def) => {\n $ZodULID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodXID = /* @__PURE__ */ $constructor(\"ZodXID\", (inst, def) => {\n $ZodXID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodKSUID = /* @__PURE__ */ $constructor(\"ZodKSUID\", (inst, def) => {\n $ZodKSUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv4 = /* @__PURE__ */ $constructor(\"ZodIPv4\", (inst, def) => {\n $ZodIPv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv6 = /* @__PURE__ */ $constructor(\"ZodIPv6\", (inst, def) => {\n $ZodIPv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv4 = /* @__PURE__ */ $constructor(\"ZodCIDRv4\", (inst, def) => {\n $ZodCIDRv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv6 = /* @__PURE__ */ $constructor(\"ZodCIDRv6\", (inst, def) => {\n $ZodCIDRv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64 = /* @__PURE__ */ $constructor(\"ZodBase64\", (inst, def) => {\n $ZodBase64.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64URL = /* @__PURE__ */ $constructor(\"ZodBase64URL\", (inst, def) => {\n $ZodBase64URL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodE164 = /* @__PURE__ */ $constructor(\"ZodE164\", (inst, def) => {\n $ZodE164.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodJWT = /* @__PURE__ */ $constructor(\"ZodJWT\", (inst, def) => {\n $ZodJWT.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUnknown = /* @__PURE__ */ $constructor(\"ZodUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unknownProcessor(inst, ctx, json, params);\n});\nfunction unknown() {\n return _unknown(ZodUnknown);\n}\nconst ZodNever = /* @__PURE__ */ $constructor(\"ZodNever\", (inst, def) => {\n $ZodNever.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => neverProcessor(inst, ctx, json, params);\n});\nfunction never(params) {\n return _never(ZodNever, params);\n}\nconst ZodArray = /* @__PURE__ */ $constructor(\"ZodArray\", (inst, def) => {\n $ZodArray.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);\n inst.element = def.element;\n inst.min = (minLength, params) => inst.check(_minLength(minLength, params));\n inst.nonempty = (params) => inst.check(_minLength(1, params));\n inst.max = (maxLength, params) => inst.check(_maxLength(maxLength, params));\n inst.length = (len, params) => inst.check(_length(len, params));\n inst.unwrap = () => inst.element;\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /* @__PURE__ */ $constructor(\"ZodObject\", (inst, def) => {\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n defineLazy(inst, \"shape\", () => {\n return def.shape;\n });\n inst.keyof = () => _enum(Object.keys(inst._zod.def.shape));\n inst.catchall = (catchall) => inst.clone({\n ...inst._zod.def,\n catchall\n });\n inst.passthrough = () => inst.clone({\n ...inst._zod.def,\n catchall: unknown()\n });\n inst.loose = () => inst.clone({\n ...inst._zod.def,\n catchall: unknown()\n });\n inst.strict = () => inst.clone({\n ...inst._zod.def,\n catchall: never()\n });\n inst.strip = () => inst.clone({\n ...inst._zod.def,\n catchall: void 0\n });\n inst.extend = (incoming) => {\n return extend(inst, incoming);\n };\n inst.safeExtend = (incoming) => {\n return safeExtend(inst, incoming);\n };\n inst.merge = (other) => merge(inst, other);\n inst.pick = (mask) => pick(inst, mask);\n inst.omit = (mask) => omit(inst, mask);\n inst.partial = (...args) => partial(ZodOptional, inst, args[0]);\n inst.required = (...args) => required(ZodNonOptional, inst, args[0]);\n});\nfunction object(shape, params) {\n return new ZodObject({\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n });\n}\nconst ZodUnion = /* @__PURE__ */ $constructor(\"ZodUnion\", (inst, def) => {\n $ZodUnion.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params);\n inst.options = def.options;\n});\nfunction union(options, params) {\n return new ZodUnion({\n type: \"union\",\n options,\n ...normalizeParams(params)\n });\n}\nconst ZodIntersection = /* @__PURE__ */ $constructor(\"ZodIntersection\", (inst, def) => {\n $ZodIntersection.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params);\n});\nfunction intersection(left, right) {\n return new ZodIntersection({\n type: \"intersection\",\n left,\n right\n });\n}\nconst ZodRecord = /* @__PURE__ */ $constructor(\"ZodRecord\", (inst, def) => {\n $ZodRecord.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);\n inst.keyType = def.keyType;\n inst.valueType = def.valueType;\n});\nfunction record(keyType, valueType, params) {\n return new ZodRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodEnum = /* @__PURE__ */ $constructor(\"ZodEnum\", (inst, def) => {\n $ZodEnum.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json, params);\n inst.enum = def.entries;\n inst.options = Object.values(def.entries);\n const keys = new Set(Object.keys(def.entries));\n inst.extract = (values, params) => {\n const newEntries = {};\n for (const value of values) if (keys.has(value)) newEntries[value] = def.entries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n inst.exclude = (values, params) => {\n const newEntries = { ...def.entries };\n for (const value of values) if (keys.has(value)) delete newEntries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n});\nfunction _enum(values, params) {\n return new ZodEnum({\n type: \"enum\",\n entries: Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values,\n ...normalizeParams(params)\n });\n}\nconst ZodTransform = /* @__PURE__ */ $constructor(\"ZodTransform\", (inst, def) => {\n $ZodTransform.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx, json, params);\n inst._zod.parse = (payload, _ctx) => {\n if (_ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n payload.addIssue = (issue$1) => {\n if (typeof issue$1 === \"string\") payload.issues.push(issue(issue$1, payload.value, def));\n else {\n const _issue = issue$1;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n _issue.input ?? (_issue.input = payload.value);\n _issue.inst ?? (_issue.inst = inst);\n payload.issues.push(issue(_issue));\n }\n };\n const output = def.transform(payload.value, payload);\n if (output instanceof Promise) return output.then((output) => {\n payload.value = output;\n return payload;\n });\n payload.value = output;\n return payload;\n };\n});\nfunction transform(fn) {\n return new ZodTransform({\n type: \"transform\",\n transform: fn\n });\n}\nconst ZodOptional = /* @__PURE__ */ $constructor(\"ZodOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction optional(innerType) {\n return new ZodOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodExactOptional = /* @__PURE__ */ $constructor(\"ZodExactOptional\", (inst, def) => {\n $ZodExactOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction exactOptional(innerType) {\n return new ZodExactOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodNullable = /* @__PURE__ */ $constructor(\"ZodNullable\", (inst, def) => {\n $ZodNullable.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nullable(innerType) {\n return new ZodNullable({\n type: \"nullable\",\n innerType\n });\n}\nconst ZodDefault = /* @__PURE__ */ $constructor(\"ZodDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeDefault = inst.unwrap;\n});\nfunction _default(innerType, defaultValue) {\n return new ZodDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodPrefault = /* @__PURE__ */ $constructor(\"ZodPrefault\", (inst, def) => {\n $ZodPrefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction prefault(innerType, defaultValue) {\n return new ZodPrefault({\n type: \"prefault\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodNonOptional = /* @__PURE__ */ $constructor(\"ZodNonOptional\", (inst, def) => {\n $ZodNonOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nonoptional(innerType, params) {\n return new ZodNonOptional({\n type: \"nonoptional\",\n innerType,\n ...normalizeParams(params)\n });\n}\nconst ZodCatch = /* @__PURE__ */ $constructor(\"ZodCatch\", (inst, def) => {\n $ZodCatch.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeCatch = inst.unwrap;\n});\nfunction _catch(innerType, catchValue) {\n return new ZodCatch({\n type: \"catch\",\n innerType,\n catchValue: typeof catchValue === \"function\" ? catchValue : () => catchValue\n });\n}\nconst ZodPipe = /* @__PURE__ */ $constructor(\"ZodPipe\", (inst, def) => {\n $ZodPipe.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);\n inst.in = def.in;\n inst.out = def.out;\n});\nfunction pipe(in_, out) {\n return new ZodPipe({\n type: \"pipe\",\n in: in_,\n out\n });\n}\nconst ZodReadonly = /* @__PURE__ */ $constructor(\"ZodReadonly\", (inst, def) => {\n $ZodReadonly.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction readonly(innerType) {\n return new ZodReadonly({\n type: \"readonly\",\n innerType\n });\n}\nconst ZodCustom = /* @__PURE__ */ $constructor(\"ZodCustom\", (inst, def) => {\n $ZodCustom.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);\n});\nfunction refine(fn, _params = {}) {\n return _refine(ZodCustom, fn, _params);\n}\nfunction superRefine(fn) {\n return _superRefine(fn);\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\nconst HookInputSchema = object({\n session_id: string(),\n transcript_path: string(),\n cwd: string(),\n permission_mode: string(),\n hook_event_name: string(),\n tool_name: string(),\n tool_input: record(string(), unknown())\n});\n/**\n* Read and parse the full stdin as JSON.\n* Returns a result with the validated HookInput, or the raw data and error on failure.\n*/\nasync function readHookInput() {\n const chunks = [];\n for await (const chunk of process.stdin) chunks.push(typeof chunk === \"string\" ? chunk : String(chunk));\n let parsed;\n try {\n parsed = JSON.parse(chunks.join(\"\"));\n } catch (_error) {\n return {\n ok: false,\n raw: chunks.join(\"\").slice(0, 200),\n error: \"invalid JSON\"\n };\n }\n const result = HookInputSchema.safeParse(parsed);\n if (!result.success) return {\n ok: false,\n raw: parsed,\n error: result.error.message\n };\n return {\n ok: true,\n data: result.data\n };\n}\nconst BashInputSchema = object({\n command: string(),\n description: optional(string())\n});\nconst FilePathInputSchema = object({ file_path: string() });\nconst SearchInputSchema = object({\n pattern: string(),\n path: optional(string())\n});\n/** Parse tool_input as Bash input. Returns null if invalid. */\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as file-path input (Read, Write, Edit). Returns null if invalid. */\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as search input (Glob, Grep). Returns null if invalid. */\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDecision(action, reason) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: action,\n ...action === \"deny\" && reason ? { permissionDecisionReason: reason } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\n/**\n* Resolve the audit log path relative to the hook's own location.\n* Co-located at `.claude/hooks/bst-hook.audit.local.jsonl`.\n*/\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\n/**\n* Rotate the log file if it exceeds MAX_LOG_SIZE.\n* Renames current log to `.1` and starts fresh.\n* Safe against concurrent rotation — ENOENT on rename is expected and ignored.\n*/\nfunction rotateIfNeeded(logPath) {\n try {\n if (fs.statSync(logPath).size > MAX_LOG_SIZE) {\n const rotatedPath = `${logPath}.1`;\n fs.renameSync(logPath, rotatedPath);\n }\n } catch (_error) {}\n}\n/** Whether rotation has been checked this session */\nlet rotationChecked = false;\n/**\n* Append an audit entry to the JSONL log.\n* Never throws — audit failures log to stderr and continue.\n*/\nfunction audit(entry) {\n try {\n const logPath = getLogPath();\n if (!rotationChecked) {\n rotateIfNeeded(logPath);\n rotationChecked = true;\n }\n const record = {\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n ...entry\n };\n fs.appendFileSync(logPath, JSON.stringify(record) + \"\\n\");\n } catch (err) {\n process.stderr.write(`[bst-hook] audit write failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n}\n\n//#endregion\n//#region src/lib/shell-tokenizer.ts\n/**\n* Quote-aware shell tokenizer (shlex port).\n*\n* Given a single command segment (no &&/||/;/|), tokenize it into words:\n* - Single quotes: literal, no escape processing\n* - Double quotes: allow \\\", \\\\, \\$, \\` escapes\n* - Backslash outside quotes: escapes next character\n* - Unquoted whitespace splits tokens\n*/\nfunction tokenize(input) {\n const tokens = [];\n let current = \"\";\n let i = 0;\n let inToken = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"'\") {\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated single quote\"\n };\n i++;\n } else if (ch === \"\\\"\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"\\\"\") {\n if (input[i] === \"\\\\\" && i + 1 < input.length) {\n const next = input[i + 1];\n if (next === \"\\\"\" || next === \"\\\\\" || next === \"$\" || next === \"`\") {\n current += next;\n i += 2;\n continue;\n }\n }\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated double quote\"\n };\n i++;\n } else if (ch === \"\\\\\" && i + 1 < input.length) {\n inToken = true;\n current += input[i + 1];\n i += 2;\n } else if (ch === \" \" || ch === \" \") {\n if (inToken) {\n tokens.push(current);\n current = \"\";\n inToken = false;\n }\n i++;\n } else {\n inToken = true;\n current += ch;\n i++;\n }\n }\n if (inToken) tokens.push(current);\n return {\n ok: true,\n tokens\n };\n}\n/**\n* Characters safe to leave unquoted when re-emitting a token. Anything outside\n* this set could be interpreted by the shell-splitter or tokenizer on a\n* subsequent pass (operators, whitespace, quotes, globs, vars, etc.).\n*/\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\n/** Single-quote-wrap a token unless it only contains safe characters. */\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n/**\n* Inverse of `tokenize`: join tokens into a command string that survives\n* round-tripping through `splitCommand` and `tokenize`.\n*\n* Naive `tokens.join(\" \")` is lossy: any token that was originally quoted\n* loses its quote wrapper and may be mis-parsed when split/tokenized again\n* (e.g. `--testPathPattern=a|b` would split on the embedded `|`).\n*/\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\n/**\n* Split a raw command string into segments on unquoted &&, ||, ;, and | operators.\n* Quote-aware: operators inside single or double quotes are not split points.\n*\n* Returns segments with the operator that follows each one (null for the last segment).\n*/\nfunction splitCommand(input) {\n if (!input || !input.trim()) return [];\n const segments = [];\n let current = \"\";\n let i = 0;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\"\" && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\\\" && !inSingleQuote && i + 1 < input.length) {\n current += ch + input[i + 1];\n i += 2;\n continue;\n }\n if (!inSingleQuote && !inDoubleQuote) {\n if (ch === \"&\" && i + 1 < input.length && input[i + 1] === \"&\") {\n pushSegment(segments, current, \"&&\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"&\" && !(i + 1 < input.length && input[i + 1] === \">\") && !(i > 0 && (input[i - 1] === \">\" || input[i - 1] === \"<\"))) {\n pushSegment(segments, current, \"&\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \"|\" && i + 1 < input.length && input[i + 1] === \"|\") {\n pushSegment(segments, current, \"||\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"|\") {\n pushSegment(segments, current, \"|\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \";\") {\n pushSegment(segments, current, \";\");\n current = \"\";\n i++;\n continue;\n }\n }\n current += ch;\n i++;\n }\n const trimmed = current.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator: null\n });\n return segments;\n}\nfunction pushSegment(segments, text, operator) {\n const trimmed = text.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator\n });\n}\n\n//#endregion\n//#region src/lib/injection-guard.ts\n/** Safe redirect patterns that don't constitute file writes */\nconst SAFE_REDIRECTS = new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n]);\n/**\n* Detect dangerous patterns in tokenized command segments.\n*\n* Checks for:\n* - Command substitution (`$(...)`, backticks)\n* - Process substitution (`<(...)`, `>(...)`)\n* - File redirects (`>`, `>>`, `<` to non-/dev/null targets) — skipped when `skipRedirects` is true\n* - Newlines in raw segment (injection via line splitting)\n*\n* @param skipRedirects When true, skip redirect analysis (used for trusted resolved scripts)\n*/\nfunction checkInjection(tokens, rawSegment, skipRedirects = false) {\n if (/[\\n\\r]/.test(rawSegment)) return {\n safe: false,\n reason: \"unquoted newline in command\"\n };\n for (const token of tokens) {\n if (token === \"`\") return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"$(\")) return {\n safe: false,\n reason: \"dollar command substitution\"\n };\n if (token.includes(\"`\")) return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"<(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n if (token.includes(\">(\") && !SAFE_REDIRECTS.has(token)) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n if (!skipRedirects) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (SAFE_REDIRECTS.has(token)) continue;\n if (token.includes(\"<\") && !token.includes(\"<(\")) return {\n safe: false,\n reason: \"input redirect\"\n };\n if (token.includes(\">\")) {\n if (token.endsWith(\">/dev/null\")) continue;\n if (token.match(/^(&>>|&>|>>|>|1>|2>)$/)) {\n if (tokens[i + 1] === \"/dev/null\") continue;\n }\n return {\n safe: false,\n reason: \"output redirect to file\"\n };\n }\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\n/**\n* File extensions considered safe documentation templates.\n* Files ending with these suffixes are NOT treated as secret .env files.\n*/\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\n/**\n* Pattern matching .env files: `.env`, `.env.local`, `.env.production`, etc.\n* Does NOT match `.env.sample`, `.env.example`, `.env.template`.\n*/\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\n/** Check if a filename is a safe .env documentation template. */\nfunction isEnvSafeSuffix(filename) {\n return ENV_SAFE_SUFFIXES.some((suffix) => filename.endsWith(suffix));\n}\n/**\n* Check if a path targets a .env file (excluding safe templates).\n* Works with both bare filenames and full paths.\n*/\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n/**\n* Check if any non-flag token in a Bash command targets a .env file.\n* Used by Bash deny rules for cat, head, tail, grep, etc.\n*/\nfunction tokensTargetEnvFile(tokens) {\n return tokens.slice(1).some((t) => !t.startsWith(\"-\") && isEnvFile(t));\n}\n\n//#endregion\n//#region src/rules.ts\nconst GIT_READ_OPS = new Set([\n \"status\",\n \"log\",\n \"diff\",\n \"show\",\n \"blame\",\n \"grep\",\n \"shortlog\",\n \"range-diff\",\n \"describe\",\n \"branch\",\n \"remote\",\n \"fetch\",\n \"ls-files\",\n \"ls-tree\",\n \"ls-remote\",\n \"show-ref\",\n \"for-each-ref\",\n \"rev-parse\",\n \"rev-list\",\n \"cat-file\",\n \"diff-tree\",\n \"name-rev\",\n \"merge-base\",\n \"check-ignore\"\n]);\n/** Git subcommands that modify the working tree, index, or history. */\nconst GIT_WRITE_OPS = new Set([\n \"push\",\n \"add\",\n \"commit\",\n \"reset\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"checkout\",\n \"switch\",\n \"stash\",\n \"tag\",\n \"revert\",\n \"clean\",\n \"rm\",\n \"mv\"\n]);\n/** Flags on git that enable arbitrary code execution */\nconst GIT_DANGEROUS_FLAGS = new Set([\n \"-c\",\n \"--exec-path\",\n \"--pager\",\n \"--upload-pack\",\n \"--receive-pack\"\n]);\n/** Git global flags that consume the next token as an argument. */\nconst GIT_FLAGS_WITH_ARG = new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/** Extract the git subcommand (first non-flag token after \"git\"), skipping flag arguments. */\nfunction getGitSubcommand(tokens) {\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n return t;\n }\n}\nfunction isGitReadOp(tokens) {\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\") || t.startsWith(\"--output=\")) return false;\n }\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n return stashAction === \"list\" || stashAction === void 0;\n }\n return GIT_READ_OPS.has(sub);\n}\nfunction isGitWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === \"list\" || stashAction === void 0) return false;\n }\n return GIT_WRITE_OPS.has(sub);\n}\n/** Safe stash actions that don't risk data loss. */\nconst STASH_SAFE_ACTIONS = new Set([\n \"push\",\n \"pop\",\n \"apply\",\n \"show\"\n]);\n/**\n* Git write operations that are safe to auto-approve:\n* - `git add` — staging is trivially reversible\n* - `git checkout -b/-B` — branch creation\n* - `git switch -c/-C` — branch creation\n* - `git switch <branch>` — branch switching (switch only handles branches, unlike checkout)\n* - `git stash` / `git stash push|pop|apply|show` — reversible stash operations\n*/\nfunction isGitSafeWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"add\") return true;\n if (sub === \"checkout\") return tokens.slice(tokens.indexOf(\"checkout\") + 1).some((t) => t === \"-b\" || t === \"-B\");\n if (sub === \"switch\") return true;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === void 0) return true;\n return STASH_SAFE_ACTIONS.has(stashAction);\n }\n return false;\n}\n/** pnpm built-in subcommands (not user-defined scripts) */\nconst PNPM_BUILTINS = new Set([\n \"install\",\n \"i\",\n \"add\",\n \"remove\",\n \"rm\",\n \"uninstall\",\n \"un\",\n \"update\",\n \"up\",\n \"upgrade\",\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"audit\",\n \"licenses\",\n \"init\",\n \"create\",\n \"link\",\n \"unlink\",\n \"pack\",\n \"publish\",\n \"config\",\n \"set\",\n \"get\",\n \"store\",\n \"cache\",\n \"rebuild\",\n \"rb\",\n \"prune\",\n \"dedupe\",\n \"dlx\",\n \"exec\",\n \"run\",\n \"patch\",\n \"patch-commit\",\n \"recursive\",\n \"-r\"\n]);\n/** Read-only pnpm commands that are always safe */\nconst PNPM_SAFE_BUILTINS = new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\"\n]);\nfunction isPnpmSafe(tokens) {\n const subTokens = tokens.slice(1);\n let subcommand;\n for (const t of subTokens) {\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n subcommand = t;\n break;\n }\n if (!subcommand) return false;\n if (PNPM_SAFE_BUILTINS.has(subcommand)) return true;\n if (subcommand === \"install\" || subcommand === \"i\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).every((t) => t.startsWith(\"-\"));\n return false;\n}\nconst NPM_READ_OPS = new Set([\n \"view\",\n \"info\",\n \"show\"\n]);\nfunction isNpmReadOp(tokens) {\n const sub = tokens[1];\n return sub !== void 0 && NPM_READ_OPS.has(sub);\n}\n/** Map npm subcommand to a pnpm redirect message */\nfunction getNpmRedirectReason(tokens) {\n const sub = tokens[1];\n const BASE = \"This project uses pnpm.\";\n if (!sub) return `${BASE} Use \\`pnpm\\` instead of npm.`;\n if (sub === \"install\" || sub === \"i\" || sub === \"ci\") {\n if (tokens.slice(2).some((t) => !t.startsWith(\"-\"))) return `${BASE} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`;\n return `${BASE} Use \\`pnpm install\\` instead of npm.`;\n }\n if (sub === \"add\") return `${BASE} Use \\`pnpm add <pkg>\\` instead of npm.`;\n if (sub === \"uninstall\" || sub === \"remove\" || sub === \"rm\" || sub === \"un\" || sub === \"r\") return `${BASE} Use \\`pnpm remove <pkg>\\` instead of npm.`;\n if (sub === \"run\" || sub === \"run-script\") return `${BASE} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`;\n if (sub === \"test\" || sub === \"t\" || sub === \"tst\") return `${BASE} Use \\`pnpm test\\` instead of npm.`;\n if (sub === \"start\") return `${BASE} Use \\`pnpm start\\` instead of npm.`;\n if (sub === \"stop\") return `${BASE} Use \\`pnpm stop\\` instead of npm.`;\n if (sub === \"restart\") return `${BASE} Use \\`pnpm restart\\` instead of npm.`;\n if (sub === \"exec\") return `${BASE} Use \\`pnpm exec <cmd>\\` instead of npm.`;\n if (sub === \"list\" || sub === \"ls\" || sub === \"ll\" || sub === \"la\") return `${BASE} Use \\`pnpm list\\` instead of npm.`;\n if (sub === \"why\") return `${BASE} Use \\`pnpm why\\` instead of npm.`;\n if (sub === \"outdated\") return `${BASE} Use \\`pnpm outdated\\` instead of npm.`;\n if (sub === \"audit\") return `${BASE} Use \\`pnpm audit\\` instead of npm.`;\n if (sub === \"update\" || sub === \"up\" || sub === \"upgrade\") return `${BASE} Use \\`pnpm update\\` instead of npm.`;\n if (sub === \"init\") return `${BASE} Use \\`pnpm init\\` instead of npm.`;\n if (sub === \"publish\") return `${BASE} Use \\`pnpm publish\\` instead of npm.`;\n if (sub === \"link\") return `${BASE} Use \\`pnpm link\\` instead of npm.`;\n if (sub === \"unlink\") return `${BASE} Use \\`pnpm unlink\\` instead of npm.`;\n if (sub === \"pack\") return `${BASE} Use \\`pnpm pack\\` instead of npm.`;\n if (sub === \"cache\") return `${BASE} Use \\`pnpm store\\` instead of \\`npm cache\\`.`;\n if (sub === \"config\" || sub === \"set\" || sub === \"get\") return `${BASE} Use \\`pnpm config\\` instead of npm.`;\n return `${BASE} Use the equivalent pnpm command instead of npm.`;\n}\nfunction arePathArgsSafe(tokens, ctx) {\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n if (path.basename(t).startsWith(\".\")) return false;\n if (t.startsWith(\"~/\") || t === \"~\") return false;\n const resolvedPath = path.resolve(ctx.cwd, t);\n if (resolvedPath !== ctx.cwd && !resolvedPath.startsWith(ctx.cwd + path.sep)) return false;\n }\n return true;\n}\nconst FIND_ALWAYS_DANGEROUS = new Set([\n \"-ok\",\n \"-okdir\",\n \"-delete\",\n \"-fprint\",\n \"-fprint0\",\n \"-fprintf\",\n \"-fls\"\n]);\nconst FIND_EXEC_SAFE_COMMANDS = new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"grep\",\n \"egrep\",\n \"fgrep\",\n \"ls\",\n \"wc\",\n \"file\",\n \"stat\",\n \"basename\",\n \"dirname\",\n \"realpath\",\n \"readlink\",\n \"sha256sum\",\n \"sha1sum\",\n \"md5sum\",\n \"echo\"\n]);\n/**\n* Extracts the command name from a `-exec` or `-execdir` block.\n* Returns the basename of the first token after `-exec`/`-execdir`,\n* or undefined if the block is malformed.\n*/\nfunction findExecCommand(tokens, startIdx) {\n const cmdToken = tokens[startIdx + 1];\n if (!cmdToken || cmdToken === \"{}\" || cmdToken === \";\" || cmdToken === \"+\") return void 0;\n return path.basename(cmdToken);\n}\nfunction isFindSafe(tokens) {\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n if (t === void 0) continue;\n if (FIND_ALWAYS_DANGEROUS.has(t)) return false;\n if (t === \"-exec\" || t === \"-execdir\") {\n const cmd = findExecCommand(tokens, i);\n if (!cmd || !FIND_EXEC_SAFE_COMMANDS.has(cmd)) return false;\n }\n }\n return true;\n}\nfunction isSortSafe(tokens) {\n return !tokens.includes(\"-o\") && !tokens.some((t) => t.startsWith(\"--output\"));\n}\nfunction isRmRecursive(tokens) {\n const rmIdx = tokens.findIndex((t) => path.basename(t) === \"rm\");\n if (rmIdx === -1) return false;\n const afterRm = tokens.slice(rmIdx + 1);\n const shortFlags = afterRm.filter((t) => t.startsWith(\"-\") && !t.startsWith(\"--\"));\n const longFlags = afterRm.filter((t) => t.startsWith(\"--\"));\n const hasRecursiveShort = shortFlags.some((f) => /[rR]/.test(f));\n const hasRecursiveLong = longFlags.includes(\"--recursive\");\n return hasRecursiveShort || hasRecursiveLong;\n}\nfunction isChmod777(tokens) {\n return tokens.some((t) => t === \"777\" || t === \"0777\");\n}\nfunction isGitForcePush(tokens) {\n const pushIdx = tokens.indexOf(\"push\");\n if (pushIdx === -1) return false;\n const afterPush = tokens.slice(pushIdx + 1);\n return afterPush.some((t) => t === \"--force\" || t === \"-f\" && !afterPush.includes(\"--force-with-lease\"));\n}\nfunction isGitResetHardOnMain(tokens) {\n if (!(tokens.includes(\"reset\") && tokens.includes(\"--hard\"))) return false;\n if (tokens.some((t) => t === \"main\" || t === \"master\")) return true;\n const positionalArgs = tokens.filter((t) => t !== \"git\" && t !== \"reset\" && !t.startsWith(\"-\"));\n return positionalArgs.length === 0 || positionalArgs.includes(\"HEAD\");\n}\n/** bst subcommands that are safe to auto-approve (read-only or local-only). */\nconst BST_SAFE_SUBCOMMANDS = new Set([\n \"checks:run\",\n \"hooks:audit\",\n \"docker:build\",\n \"docker:check\",\n \"repo:sync\"\n]);\nfunction isBstSafe(tokens) {\n const sub = tokens[1];\n if (!sub) return false;\n return BST_SAFE_SUBCOMMANDS.has(sub);\n}\nconst denyRules = [\n {\n name: \"rm-recursive\",\n match: /^rm$/,\n action: \"deny\",\n when: (tokens) => isRmRecursive(tokens),\n reason: \"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"\n },\n {\n name: \"sudo\",\n match: /^sudo$/,\n action: \"deny\",\n reason: \"Don't use sudo. If you need elevated permissions, ask me to do it manually.\"\n },\n {\n name: \"git-force-push\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitForcePush(tokens),\n reason: \"Use `git push --force-with-lease` instead of `--force`.\"\n },\n {\n name: \"git-reset-hard\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitResetHardOnMain(tokens),\n reason: \"Don't `reset --hard` on main. Create a branch first.\"\n },\n {\n name: \"git-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitWriteOp(tokens) && !isGitSafeWriteOp(tokens),\n reason: \"Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"env-file-read\",\n match: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n }\n];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitSafeWriteOp(tokens)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({ allow: array(string()) });\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error — safe default is to not trust the file.\n*/\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n return true;\n } catch {\n return false;\n }\n}\n/**\n* Load repo-level allow patterns from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns an empty array if the file is missing, invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n if (!existsSync(settingsFile)) return [];\n if (!isFileClean(root, SETTINGS_PATH)) return [];\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return [];\n return result.data.allow.filter((s) => typeof s === \"string\" && s.length > 0);\n } catch {\n return [];\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\n*/\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\n*/\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\n/**\n* Find the pnpm workspace root by walking up from `cwd` until a\n* `pnpm-workspace.yaml` file is found. Returns `null` if none is found\n* before reaching the filesystem root.\n*/\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n/**\n* List all workspace package directories.\n*/\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const parentDir = resolve(rootDir, pattern.slice(0, -2));\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\n*/\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\n*/\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\n/**\n* Read the name field from package.json in the given directory.\n*/\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\n*/\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n/**\n* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) — safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\n*/\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n const injection = checkInjection(tokens, rawSegment, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\n }\n const SAFE_REDIRECT_TOKENS = new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"node eval\"\n };\n if (cmdName === \"mv\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"mv\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\n*/\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\n*/\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` flag (both `--filter <pkg>` and `--filter=<pkg>`\n* forms), resolves each against pnpm-workspace.yaml, then classifies the\n* subcommand as built-in or script. Script commands are resolved and evaluated\n* in each filtered package — all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\n*/\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx) {\n const filterPkgs = [];\n let cursor = filterIdx;\n while (cursor < subTokens.length) {\n const t = subTokens[cursor];\n if (t === \"--filter\") {\n const pkg = subTokens[cursor + 1];\n if (!pkg || pkg.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\")) {\n const pkg = t.slice(9);\n if (!pkg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ]).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c → passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\nconst XARGS_ARG_FLAGS = new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\n/** xargs flags that are boolean (no argument) */\nconst XARGS_BOOL_FLAGS = new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\n*/\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = new Set([\"Write\", \"Edit\"]);\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n const hooksDir = path.join(ctx.cwd, \".claude\", \"hooks\");\n return resolved.startsWith(hooksDir + path.sep) || resolved === hooksDir;\n },\n reason: \"Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\"\n },\n {\n name: \"claude-settings-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n return path.resolve(ctx.cwd, filePath) === path.join(ctx.cwd, \".claude\", \"settings.json\");\n },\n reason: \"Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\"\n },\n {\n name: \"outside-cwd-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n if (resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep)) return false;\n const homeClaudeDir = path.join(os.homedir(), \".claude\");\n if (resolved === homeClaudeDir || resolved.startsWith(homeClaudeDir + path.sep)) return false;\n return true;\n },\n reason: \"Don't write files outside the project directory.\"\n }\n];\nconst fileAllowRules = [{\n name: \"cwd-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n return resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep);\n }\n}, {\n name: \"cwd-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n return resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep);\n }\n}];\n\n//#endregion\n//#region src/evaluate-tool.ts\n/** Tools whose input contains a `file_path` field. */\nconst FILE_PATH_TOOLS = new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\n/** Tools whose input contains a `path` field (search root, optional). */\nconst SEARCH_TOOLS = new Set([\"Glob\", \"Grep\"]);\n/**\n* Extract the relevant file path from tool input based on tool name.\n* Returns null if the input shape doesn't match expectations.\n*/\nfunction extractPath(toolName, toolInput, cwd) {\n if (FILE_PATH_TOOLS.has(toolName)) return parseFilePathInput(toolInput)?.file_path ?? null;\n if (SEARCH_TOOLS.has(toolName)) return parseSearchInput(toolInput)?.path ?? cwd;\n return null;\n}\n/**\n* Evaluate a non-Bash tool invocation against file-tool rules.\n* Returns allow, deny, or passthrough.\n*/\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"passthrough\",\n rule: null,\n reason: \"unknown tool input shape\"\n };\n for (const rule of fileDenyRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n for (const rule of fileAllowRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"no file rule matched\"\n };\n}\n\n//#endregion\n//#region src/index.ts\nconst inputResult = await readHookInput();\nif (!inputResult.ok) {\n process.stderr.write(`[bst-hook] invalid hook input: ${inputResult.error}\\n`);\n audit({\n sessionId: \"unknown\",\n cwd: \"unknown\",\n command: `<invalid input: ${inputResult.error}>`,\n decision: \"passthrough\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`\n });\n process.exit(0);\n}\nconst input = inputResult.data;\nif (input.hook_event_name !== \"PreToolUse\") process.exit(0);\nif (!new Set([\n \"Bash\",\n \"Read\",\n \"Write\",\n \"Edit\",\n \"Glob\",\n \"Grep\"\n]).has(input.tool_name)) process.exit(0);\nconst repoAllowPatterns = loadRepoSettings(input.cwd);\nlet decision;\nlet auditCommand;\nif (input.tool_name === \"Bash\") {\n const bashInput = parseBashInput(input.tool_input);\n if (!bashInput) {\n audit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: `<invalid Bash input: ${JSON.stringify(input.tool_input).slice(0, 200)}>`,\n decision: \"passthrough\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\"\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoAllowPatterns\n });\n auditCommand = bashInput.command;\n} else {\n const projectDir = findWorkspaceRoot(input.cwd) ?? process.env[\"CLAUDE_PROJECT_DIR\"] ?? input.cwd;\n decision = evaluateToolUse(input.tool_name, input.tool_input, { cwd: projectDir });\n const filePath = String(input.tool_input[\"file_path\"] ?? input.tool_input[\"path\"] ?? \"\");\n auditCommand = `${input.tool_name} ${filePath}`;\n}\naudit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: auditCommand,\n decision: decision.action,\n rule: decision.rule,\n reason: decision.reason\n});\nif (decision.action === \"allow\") writeDecision(\"allow\");\nelse if (decision.action === \"deny\") writeDecision(\"deny\", decision.reason);\n\n//#endregion\nexport { };", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** — blocked with a redirect message\n- **Allowed** — auto-approved, no prompt\n- **Passthrough** — falls through to Claude Code's normal permission prompt\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync)\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, branch, remote, fetch, rev-parse, etc.)\n- **Git safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory)\n\n### File tools\n\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv` (file moves)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
hooks: z.array(HookEntrySchema).default([])
|
|
3531
|
-
});
|
|
3532
|
-
const ClaudeSettingsSchema = z.object({
|
|
3533
|
-
permissions: z.object({
|
|
3534
|
-
allow: z.array(z.string()).default([]),
|
|
3535
|
-
deny: z.array(z.string()).default([])
|
|
3536
|
-
}).default({
|
|
3537
|
-
allow: [],
|
|
3538
|
-
deny: []
|
|
3539
|
-
}),
|
|
3540
|
-
hooks: z.record(z.string(), z.array(HookMatcherSchema)).default({}),
|
|
3541
|
-
instructions: z.array(z.string()).default([]),
|
|
3542
|
-
enabledPlugins: z.record(z.string(), z.boolean()).default({}),
|
|
3543
|
-
extraKnownMarketplaces: z.record(z.string(), z.record(z.string(), z.unknown())).default({})
|
|
3544
|
-
});
|
|
3545
|
-
function parseClaudeSettings(raw) {
|
|
3546
|
-
try {
|
|
3547
|
-
const json = JSON.parse(raw);
|
|
3548
|
-
const rawResult = z.record(z.string(), z.unknown()).safeParse(json);
|
|
3549
|
-
const settingsResult = ClaudeSettingsSchema.safeParse(json);
|
|
3550
|
-
if (!rawResult.success || !settingsResult.success) return void 0;
|
|
3551
|
-
return {
|
|
3552
|
-
settings: settingsResult.data,
|
|
3553
|
-
rawJson: rawResult.data
|
|
3554
|
-
};
|
|
3555
|
-
} catch {
|
|
3556
|
-
return;
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
function buildSettings(ctx) {
|
|
3560
|
-
const allow = [];
|
|
3561
|
-
const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
|
|
3562
|
-
const extraKnownMarketplaces = {};
|
|
3563
|
-
if (ctx.config.structure === "monorepo") {
|
|
3564
|
-
if (getMonorepoPackages(ctx.targetDir).some((pkg) => hasWebUIDeps(pkg.dir))) enabledPlugins["frontend-design@claude-plugins-official"] = true;
|
|
3565
|
-
} else if (ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) enabledPlugins["frontend-design@claude-plugins-official"] = true;
|
|
3566
|
-
return {
|
|
3567
|
-
permissions: {
|
|
3568
|
-
allow,
|
|
3569
|
-
deny: []
|
|
3570
|
-
},
|
|
3571
|
-
hooks: { PreToolUse: [{
|
|
3572
|
-
matcher: "Bash|Read|Write|Edit|Glob|Grep",
|
|
3573
|
-
hooks: [{
|
|
3574
|
-
type: "command",
|
|
3575
|
-
command: `node "$(git rev-parse --show-toplevel)/.claude/hooks/bst-hook.mjs"`
|
|
3576
|
-
}]
|
|
3577
|
-
}] },
|
|
3578
|
-
instructions: [
|
|
3579
|
-
"Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
|
|
3580
|
-
"No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
|
|
3581
|
-
"Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option. For no-empty-function: add a `{ /* no-op */ }` comment body instead of a disable comment.",
|
|
3582
|
-
"Prefer extensionless imports; if an extension is required, use .ts over .js.",
|
|
3583
|
-
"The bst-hook manages Bash/file permissions. See .claude/hooks/bst-hook-guide.md for auto-approved, denied, and passthrough commands."
|
|
3584
|
-
],
|
|
3585
|
-
enabledPlugins,
|
|
3586
|
-
extraKnownMarketplaces
|
|
3474
|
+
const isGitHub = ctx.config.ci === "github";
|
|
3475
|
+
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
3476
|
+
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
3477
|
+
const hasDocker = hasDockerPackages(ctx);
|
|
3478
|
+
const hasAssets = hasReleaseAssets(ctx);
|
|
3479
|
+
const registryDerivable = hasRepositoryUrl(ctx);
|
|
3480
|
+
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
3481
|
+
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, hasAssets, registryDerivable);
|
|
3482
|
+
if (!steps) return {
|
|
3483
|
+
filePath,
|
|
3484
|
+
action: "skipped",
|
|
3485
|
+
description: "Release CI workflow not applicable"
|
|
3587
3486
|
};
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3487
|
+
if (ctx.config.releaseStrategy === "release-it") steps.push(postReleaseHookStep({
|
|
3488
|
+
command: getPostReleaseHookCommand(ctx),
|
|
3489
|
+
secrets: hookSecrets
|
|
3490
|
+
}));
|
|
3491
|
+
else if (ctx.config.releaseStrategy === "simple") steps.push(postReleaseHookStep({
|
|
3492
|
+
command: getPostReleaseHookCommand(ctx),
|
|
3493
|
+
secrets: hookSecrets,
|
|
3494
|
+
condition: "steps.release.outputs.pushed == 'true'"
|
|
3495
|
+
}));
|
|
3496
|
+
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
3497
|
+
const content = buildWorkflowYaml({
|
|
3498
|
+
ci: ctx.config.ci,
|
|
3499
|
+
name: "Release",
|
|
3500
|
+
on,
|
|
3501
|
+
...isGitHub && { permissions: { contents: "write" } },
|
|
3502
|
+
jobName: "release",
|
|
3503
|
+
steps
|
|
3597
3504
|
});
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
function serializeSettings$1(settings) {
|
|
3601
|
-
const { enabledPlugins, extraKnownMarketplaces, hooks, ...rest } = settings;
|
|
3602
|
-
const out = { ...rest };
|
|
3603
|
-
if (Object.keys(hooks).length > 0) out["hooks"] = hooks;
|
|
3604
|
-
if (Object.keys(enabledPlugins).length > 0) out["enabledPlugins"] = enabledPlugins;
|
|
3605
|
-
if (Object.keys(extraKnownMarketplaces).length > 0) out["extraKnownMarketplaces"] = extraKnownMarketplaces;
|
|
3606
|
-
return JSON.stringify(out, null, 2) + "\n";
|
|
3607
|
-
}
|
|
3608
|
-
function writeOrMergeSettings(ctx, filePath, generated) {
|
|
3609
|
-
const existing = ctx.read(filePath);
|
|
3505
|
+
const alreadyExists = ctx.exists(workflowPath);
|
|
3506
|
+
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
3610
3507
|
if (existing) {
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
filePath,
|
|
3508
|
+
if (isToolingIgnored(existing)) return {
|
|
3509
|
+
filePath: workflowPath,
|
|
3614
3510
|
action: "skipped",
|
|
3615
|
-
description: "
|
|
3511
|
+
description: "Release workflow has ignore comment"
|
|
3616
3512
|
};
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
const missingDeny = generated.permissions.deny.filter((rule) => !settings.permissions.deny.includes(rule));
|
|
3620
|
-
const mergedInstructions = [...settings.instructions];
|
|
3621
|
-
let instructionChanges = 0;
|
|
3622
|
-
for (const inst of generated.instructions) {
|
|
3623
|
-
if (mergedInstructions.includes(inst)) continue;
|
|
3624
|
-
const prefixIdx = mergedInstructions.findIndex((e) => inst.startsWith(e) || e.startsWith(inst));
|
|
3625
|
-
if (prefixIdx !== -1) mergedInstructions[prefixIdx] = inst;
|
|
3626
|
-
else mergedInstructions.push(inst);
|
|
3627
|
-
instructionChanges++;
|
|
3628
|
-
}
|
|
3629
|
-
let hookChanges = 0;
|
|
3630
|
-
const mergedHooks = { ...settings.hooks };
|
|
3631
|
-
for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
|
|
3632
|
-
const existingMatchers = mergedHooks[event] ?? [];
|
|
3633
|
-
for (const gm of generatedMatchers) {
|
|
3634
|
-
const existingIdx = existingMatchers.findIndex((m) => m.matcher === gm.matcher && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)));
|
|
3635
|
-
if (existingIdx !== -1) {
|
|
3636
|
-
const existingEntry = existingMatchers[existingIdx];
|
|
3637
|
-
const genCommand = gm.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
|
|
3638
|
-
const curCommand = existingEntry.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
|
|
3639
|
-
if (genCommand && curCommand && genCommand !== curCommand) {
|
|
3640
|
-
const target = (mergedHooks[event] ?? [])[existingIdx];
|
|
3641
|
-
target.hooks = target.hooks.map((h) => h.command.includes(BST_HOOK_MARKER) ? Object.assign(h, { command: genCommand }) : h);
|
|
3642
|
-
hookChanges++;
|
|
3643
|
-
}
|
|
3644
|
-
continue;
|
|
3645
|
-
}
|
|
3646
|
-
if (gm.matcher.includes("|")) {
|
|
3647
|
-
const before = (mergedHooks[event] ?? []).length;
|
|
3648
|
-
mergedHooks[event] = removeObsoleteBstHookEntries(mergedHooks[event] ?? [], gm.matcher);
|
|
3649
|
-
const removed = before - (mergedHooks[event] ?? []).length;
|
|
3650
|
-
if (removed > 0) hookChanges += removed;
|
|
3651
|
-
}
|
|
3652
|
-
mergedHooks[event] = [...mergedHooks[event] ?? [], gm];
|
|
3653
|
-
hookChanges++;
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in settings.enabledPlugins));
|
|
3657
|
-
const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in settings.extraKnownMarketplaces));
|
|
3658
|
-
const changed = missingAllow.length + missingDeny.length + hookChanges + instructionChanges + missingPlugins.length + missingMarketplaces.length;
|
|
3659
|
-
if (changed === 0) return {
|
|
3660
|
-
filePath,
|
|
3513
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
3514
|
+
filePath: workflowPath,
|
|
3661
3515
|
action: "skipped",
|
|
3662
|
-
description: "
|
|
3663
|
-
};
|
|
3664
|
-
rawJson["permissions"] = {
|
|
3665
|
-
allow: [...settings.permissions.allow, ...missingAllow],
|
|
3666
|
-
deny: [...settings.permissions.deny, ...missingDeny]
|
|
3667
|
-
};
|
|
3668
|
-
if (Object.keys(mergedHooks).length > 0) rawJson["hooks"] = mergedHooks;
|
|
3669
|
-
rawJson["instructions"] = mergedInstructions;
|
|
3670
|
-
const updatedPlugins = { ...settings.enabledPlugins };
|
|
3671
|
-
for (const [key, value] of missingPlugins) updatedPlugins[key] = value;
|
|
3672
|
-
const updatedMarketplaces = { ...settings.extraKnownMarketplaces };
|
|
3673
|
-
for (const [key, value] of missingMarketplaces) updatedMarketplaces[key] = value;
|
|
3674
|
-
if (Object.keys(updatedPlugins).length > 0) rawJson["enabledPlugins"] = updatedPlugins;
|
|
3675
|
-
else delete rawJson["enabledPlugins"];
|
|
3676
|
-
if (Object.keys(updatedMarketplaces).length > 0) rawJson["extraKnownMarketplaces"] = updatedMarketplaces;
|
|
3677
|
-
else delete rawJson["extraKnownMarketplaces"];
|
|
3678
|
-
ctx.write(filePath, JSON.stringify(rawJson, null, 2) + "\n");
|
|
3679
|
-
return {
|
|
3680
|
-
filePath,
|
|
3681
|
-
action: "updated",
|
|
3682
|
-
description: `Updated ${String(changed)} rules/instructions`
|
|
3516
|
+
description: "Release workflow already up to date"
|
|
3683
3517
|
};
|
|
3684
3518
|
}
|
|
3685
|
-
|
|
3519
|
+
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
3520
|
+
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
3521
|
+
ctx.write(workflowPath, final);
|
|
3686
3522
|
return {
|
|
3687
|
-
filePath,
|
|
3688
|
-
action: "created",
|
|
3689
|
-
description: `Generated ${
|
|
3523
|
+
filePath: workflowPath,
|
|
3524
|
+
action: alreadyExists ? "updated" : "created",
|
|
3525
|
+
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
3690
3526
|
};
|
|
3691
3527
|
}
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3528
|
+
//#endregion
|
|
3529
|
+
//#region src/generators/ci.ts
|
|
3530
|
+
/** Build the release step for the check job (changesets strategy). */
|
|
3531
|
+
function changesetsReleaseStep(ci, publishesNpm) {
|
|
3532
|
+
if (ci === "github") return { step: {
|
|
3533
|
+
id: "release",
|
|
3534
|
+
uses: "changesets/action@v1",
|
|
3535
|
+
if: "github.ref == 'refs/heads/main'",
|
|
3536
|
+
with: {
|
|
3537
|
+
publish: "pnpm changeset publish",
|
|
3538
|
+
version: "pnpm changeset version"
|
|
3539
|
+
},
|
|
3540
|
+
env: {
|
|
3541
|
+
GITHUB_TOKEN: actionsExpr("github.token"),
|
|
3542
|
+
...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
3705
3543
|
}
|
|
3706
|
-
}
|
|
3707
|
-
return
|
|
3544
|
+
} };
|
|
3545
|
+
return { step: {
|
|
3546
|
+
id: "release",
|
|
3547
|
+
name: "Release",
|
|
3548
|
+
env: {
|
|
3549
|
+
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
3550
|
+
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
3551
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
3552
|
+
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") },
|
|
3553
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
3554
|
+
},
|
|
3555
|
+
run: "pnpm exec bst release:changesets"
|
|
3556
|
+
} };
|
|
3708
3557
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3558
|
+
const CI_CONCURRENCY = {
|
|
3559
|
+
group: `ci-${actionsExpr("github.ref")}`,
|
|
3560
|
+
"cancel-in-progress": actionsExpr("github.ref != 'refs/heads/main'")
|
|
3561
|
+
};
|
|
3562
|
+
function checkSteps(nodeVersionYaml, publishesNpm) {
|
|
3563
|
+
return [
|
|
3564
|
+
{ step: {
|
|
3565
|
+
uses: "actions/checkout@v6",
|
|
3566
|
+
with: { "fetch-depth": 0 }
|
|
3567
|
+
} },
|
|
3568
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
3569
|
+
{ step: {
|
|
3570
|
+
uses: "actions/setup-node@v6",
|
|
3571
|
+
with: {
|
|
3572
|
+
...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
3573
|
+
cache: "pnpm",
|
|
3574
|
+
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
3575
|
+
}
|
|
3576
|
+
} },
|
|
3577
|
+
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
3578
|
+
{ step: {
|
|
3579
|
+
name: "Run all checks",
|
|
3580
|
+
run: "pnpm ci:check"
|
|
3581
|
+
} }
|
|
3582
|
+
];
|
|
3583
|
+
}
|
|
3584
|
+
/** Resolve the CI workflow filename based on release strategy. */
|
|
3585
|
+
function ciWorkflowPath(ci, releaseStrategy) {
|
|
3586
|
+
return `${ci === "github" ? ".github/workflows" : ".forgejo/workflows"}/${releaseStrategy === "changesets" ? "ci.yml" : "check.yml"}`;
|
|
3587
|
+
}
|
|
3588
|
+
async function generateCi(ctx) {
|
|
3589
|
+
if (ctx.config.ci === "none") return {
|
|
3590
|
+
filePath: "ci",
|
|
3591
|
+
action: "skipped",
|
|
3592
|
+
description: "CI workflow not requested"
|
|
3593
|
+
};
|
|
3594
|
+
const isGitHub = ctx.config.ci === "github";
|
|
3595
|
+
const isForgejo = !isGitHub;
|
|
3596
|
+
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
3597
|
+
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
3598
|
+
const publishesNpm = ctx.config.publishNpm === true;
|
|
3599
|
+
const hookCommand = getPostReleaseHookCommand(ctx);
|
|
3600
|
+
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
3601
|
+
const hasDocker = isChangesets && hasDockerPackages(ctx);
|
|
3602
|
+
const hasAssets = isChangesets && hasReleaseAssets(ctx);
|
|
3603
|
+
const registryDerivable = hasDocker && hasRepositoryUrl(ctx);
|
|
3604
|
+
const publishedIf = "steps.release.outputs.published == 'true'";
|
|
3605
|
+
const releaseEnv = {};
|
|
3606
|
+
if (isGitHub) releaseEnv["GITHUB_TOKEN"] = actionsExpr("github.token");
|
|
3607
|
+
else {
|
|
3608
|
+
releaseEnv["FORGEJO_SERVER_URL"] = actionsExpr("github.server_url");
|
|
3609
|
+
releaseEnv["FORGEJO_REPOSITORY"] = actionsExpr("github.repository");
|
|
3610
|
+
releaseEnv["RELEASE_TOKEN"] = actionsExpr("secrets.RELEASE_TOKEN");
|
|
3611
|
+
}
|
|
3612
|
+
const steps = [
|
|
3613
|
+
...checkSteps(nodeVersionYaml, isChangesets && publishesNpm),
|
|
3614
|
+
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : [],
|
|
3615
|
+
...hasAssets ? [{ step: {
|
|
3616
|
+
name: "Upload Release Assets",
|
|
3617
|
+
if: publishedIf,
|
|
3618
|
+
env: releaseEnv,
|
|
3619
|
+
run: "pnpm exec bst release:assets"
|
|
3620
|
+
} }] : [],
|
|
3621
|
+
...hasDocker ? [{ step: {
|
|
3622
|
+
name: "Publish Docker images",
|
|
3623
|
+
if: publishedIf,
|
|
3624
|
+
env: dockerPublishEnv(registryDerivable),
|
|
3625
|
+
run: "pnpm exec bst docker:publish"
|
|
3626
|
+
} }] : [],
|
|
3627
|
+
...isChangesets ? [postReleaseHookStep({
|
|
3628
|
+
command: hookCommand,
|
|
3629
|
+
secrets: hookSecrets,
|
|
3630
|
+
condition: publishedIf
|
|
3631
|
+
})] : []
|
|
3632
|
+
];
|
|
3633
|
+
const content = buildWorkflowYaml({
|
|
3634
|
+
ci: ctx.config.ci,
|
|
3635
|
+
name: "CI",
|
|
3636
|
+
on: {
|
|
3637
|
+
push: {
|
|
3638
|
+
branches: ["main"],
|
|
3639
|
+
"tags-ignore": ["**"]
|
|
3640
|
+
},
|
|
3641
|
+
pull_request: null
|
|
3717
3642
|
},
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
};
|
|
3727
|
-
plugins["@release-it/bumper"] = { out: "packages/*/package.json" };
|
|
3728
|
-
}
|
|
3729
|
-
if (Object.keys(plugins).length > 0) config["plugins"] = plugins;
|
|
3730
|
-
return config;
|
|
3731
|
-
}
|
|
3732
|
-
async function generateReleaseIt(ctx) {
|
|
3733
|
-
const filePath = ".release-it.json";
|
|
3734
|
-
if (ctx.config.releaseStrategy !== "release-it") return {
|
|
3735
|
-
filePath,
|
|
3736
|
-
action: "skipped",
|
|
3737
|
-
description: "release-it not requested"
|
|
3738
|
-
};
|
|
3739
|
-
const content = JSON.stringify(buildConfig$3(ctx.config.ci, ctx.config.structure === "monorepo"), null, 2) + "\n";
|
|
3740
|
-
const existing = ctx.read(filePath);
|
|
3643
|
+
enableEmailNotifications: isForgejo,
|
|
3644
|
+
...isChangesets && { concurrency: CI_CONCURRENCY },
|
|
3645
|
+
jobName: "check",
|
|
3646
|
+
steps
|
|
3647
|
+
});
|
|
3648
|
+
const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
3649
|
+
const alreadyExists = ctx.exists(filePath);
|
|
3650
|
+
const existing = alreadyExists ? ctx.read(filePath) : void 0;
|
|
3741
3651
|
if (existing) {
|
|
3742
|
-
if (
|
|
3652
|
+
if (isToolingIgnored(existing)) return {
|
|
3743
3653
|
filePath,
|
|
3744
3654
|
action: "skipped",
|
|
3745
|
-
description: "
|
|
3655
|
+
description: "CI workflow has ignore comment"
|
|
3746
3656
|
};
|
|
3747
|
-
if (
|
|
3657
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
3748
3658
|
filePath,
|
|
3749
3659
|
action: "skipped",
|
|
3750
|
-
description: "
|
|
3660
|
+
description: "CI workflow already up to date"
|
|
3751
3661
|
};
|
|
3752
3662
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
action: existing ? "updated" : "created",
|
|
3757
|
-
description: "Generated .release-it.json"
|
|
3758
|
-
};
|
|
3759
|
-
}
|
|
3760
|
-
//#endregion
|
|
3761
|
-
//#region src/generators/changesets.ts
|
|
3762
|
-
function buildConfig$2() {
|
|
3763
|
-
return {
|
|
3764
|
-
$schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
3765
|
-
changelog: "@changesets/cli/changelog",
|
|
3766
|
-
commit: false,
|
|
3767
|
-
fixed: [],
|
|
3768
|
-
linked: [],
|
|
3769
|
-
access: "public",
|
|
3770
|
-
baseBranch: "main",
|
|
3771
|
-
updateInternalDependencies: "patch",
|
|
3772
|
-
ignore: []
|
|
3773
|
-
};
|
|
3774
|
-
}
|
|
3775
|
-
async function generateChangesets(ctx) {
|
|
3776
|
-
const filePath = ".changeset/config.json";
|
|
3777
|
-
if (ctx.config.releaseStrategy !== "changesets") return {
|
|
3778
|
-
filePath,
|
|
3779
|
-
action: "skipped",
|
|
3780
|
-
description: "Changesets not requested"
|
|
3781
|
-
};
|
|
3782
|
-
const content = JSON.stringify(buildConfig$2(), null, 2) + "\n";
|
|
3783
|
-
const existing = ctx.read(filePath);
|
|
3784
|
-
if (existing) return {
|
|
3785
|
-
filePath,
|
|
3786
|
-
action: "skipped",
|
|
3787
|
-
description: "Existing changesets config preserved"
|
|
3788
|
-
};
|
|
3789
|
-
ctx.write(filePath, content);
|
|
3663
|
+
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
3664
|
+
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
3665
|
+
ctx.write(filePath, final);
|
|
3790
3666
|
return {
|
|
3791
3667
|
filePath,
|
|
3792
|
-
action:
|
|
3793
|
-
description:
|
|
3668
|
+
action: alreadyExists ? "updated" : "created",
|
|
3669
|
+
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions CI workflow`
|
|
3794
3670
|
};
|
|
3795
3671
|
}
|
|
3796
3672
|
//#endregion
|
|
3797
|
-
//#region src/generators/
|
|
3798
|
-
const
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3673
|
+
//#region src/generators/knip.ts
|
|
3674
|
+
const KNIP_CONFIG_SINGLE = `import type { KnipConfig } from "knip";
|
|
3675
|
+
|
|
3676
|
+
export default {
|
|
3677
|
+
entry: ["src/index.ts"],
|
|
3678
|
+
project: ["src/**/*.ts"],
|
|
3679
|
+
ignore: ["dist/**", ".claude/hooks/**"],
|
|
3680
|
+
} satisfies KnipConfig;
|
|
3681
|
+
`;
|
|
3682
|
+
const KNIP_CONFIG_MONOREPO = `import type { KnipConfig } from "knip";
|
|
3683
|
+
|
|
3684
|
+
export default {
|
|
3685
|
+
ignore: [".claude/hooks/**"],
|
|
3686
|
+
workspaces: {
|
|
3687
|
+
".": {
|
|
3688
|
+
entry: [],
|
|
3689
|
+
project: [],
|
|
3690
|
+
},
|
|
3691
|
+
"packages/*": {
|
|
3692
|
+
entry: ["src/index.ts", "src/bin.ts"],
|
|
3693
|
+
project: ["src/**/*.ts"],
|
|
3694
|
+
ignore: ["dist/**"],
|
|
3695
|
+
},
|
|
3696
|
+
},
|
|
3697
|
+
} satisfies KnipConfig;
|
|
3698
|
+
`;
|
|
3699
|
+
const REQUIRED_IGNORE = ".claude/hooks/**";
|
|
3700
|
+
/** All known knip config file locations, in priority order. */
|
|
3701
|
+
const KNIP_CONFIG_PATHS = [
|
|
3702
|
+
"knip.config.ts",
|
|
3703
|
+
"knip.config.mts",
|
|
3704
|
+
"knip.json",
|
|
3705
|
+
"knip.jsonc",
|
|
3706
|
+
"knip.ts",
|
|
3707
|
+
"knip.mts"
|
|
3708
|
+
];
|
|
3709
|
+
const JSON_EXTENSIONS = new Set([".json", ".jsonc"]);
|
|
3710
|
+
function isJsonConfig(path) {
|
|
3711
|
+
const dot = path.lastIndexOf(".");
|
|
3712
|
+
return dot !== -1 && JSON_EXTENSIONS.has(path.slice(dot));
|
|
3810
3713
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
" stage_fixed: true",
|
|
3820
|
-
""
|
|
3821
|
-
].join("\n");
|
|
3714
|
+
/**
|
|
3715
|
+
* Try to add the required ignore pattern to an existing knip config.
|
|
3716
|
+
* Returns the updated content, or undefined if no change was needed.
|
|
3717
|
+
*/
|
|
3718
|
+
function ensureIgnorePattern(content, path) {
|
|
3719
|
+
if (content.includes(".claude/hooks")) return void 0;
|
|
3720
|
+
if (isJsonConfig(path)) return ensureIgnoreInJson(content);
|
|
3721
|
+
return ensureIgnoreInTs(content);
|
|
3822
3722
|
}
|
|
3823
|
-
const
|
|
3824
|
-
|
|
3825
|
-
const
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
"
|
|
3832
|
-
];
|
|
3833
|
-
/** All known lint-staged config file locations to archive. */
|
|
3834
|
-
const LINT_STAGED_CONFIG_PATHS = [
|
|
3835
|
-
"lint-staged.config.mjs",
|
|
3836
|
-
"lint-staged.config.js",
|
|
3837
|
-
"lint-staged.config.cjs",
|
|
3838
|
-
".lintstagedrc",
|
|
3839
|
-
".lintstagedrc.json",
|
|
3840
|
-
".lintstagedrc.yaml",
|
|
3841
|
-
".lintstagedrc.yml",
|
|
3842
|
-
".lintstagedrc.mjs",
|
|
3843
|
-
".lintstagedrc.cjs"
|
|
3844
|
-
];
|
|
3845
|
-
/** All known lefthook config file locations, in priority order. */
|
|
3846
|
-
const LEFTHOOK_CONFIG_PATHS = ["lefthook.yml", ".lefthook.yml"];
|
|
3847
|
-
/** Archive all husky hook files found in .husky/ */
|
|
3848
|
-
function archiveHuskyHooks(ctx, results) {
|
|
3849
|
-
let found = false;
|
|
3850
|
-
for (const hook of HUSKY_HOOK_NAMES) {
|
|
3851
|
-
const huskyPath = `.husky/${hook}`;
|
|
3852
|
-
const existing = ctx.read(huskyPath);
|
|
3853
|
-
if (existing !== void 0) {
|
|
3854
|
-
ctx.write(`${ARCHIVE_DIR}/${huskyPath}`, existing);
|
|
3855
|
-
ctx.remove(huskyPath);
|
|
3856
|
-
results.push({
|
|
3857
|
-
filePath: huskyPath,
|
|
3858
|
-
action: "archived",
|
|
3859
|
-
description: `Moved to ${ARCHIVE_DIR}/${huskyPath}`
|
|
3860
|
-
});
|
|
3861
|
-
found = true;
|
|
3862
|
-
}
|
|
3863
|
-
}
|
|
3864
|
-
return found;
|
|
3723
|
+
const KnipJsonSchema = z.object({ ignore: z.array(z.string()).optional() }).passthrough();
|
|
3724
|
+
function ensureIgnoreInJson(content) {
|
|
3725
|
+
const result = KnipJsonSchema.safeParse(JSON.parse(content));
|
|
3726
|
+
if (!result.success) return void 0;
|
|
3727
|
+
const parsed = result.data;
|
|
3728
|
+
const ignore = parsed.ignore ?? [];
|
|
3729
|
+
ignore.push(REQUIRED_IGNORE);
|
|
3730
|
+
parsed.ignore = ignore;
|
|
3731
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
3865
3732
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
if (existing !== void 0) {
|
|
3872
|
-
ctx.write(`${ARCHIVE_DIR}/${lsPath}`, existing);
|
|
3873
|
-
ctx.remove(lsPath);
|
|
3874
|
-
results.push({
|
|
3875
|
-
filePath: lsPath,
|
|
3876
|
-
action: "archived",
|
|
3877
|
-
description: `Moved to ${ARCHIVE_DIR}/${lsPath}`
|
|
3878
|
-
});
|
|
3879
|
-
found = true;
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
return found;
|
|
3733
|
+
function ensureIgnoreInTs(content) {
|
|
3734
|
+
const ignoreArrayPattern = /(ignore:\s*\[)/;
|
|
3735
|
+
if (ignoreArrayPattern.exec(content)) return content.replace(ignoreArrayPattern, `$1"${REQUIRED_IGNORE}", `);
|
|
3736
|
+
const exportPattern = /(export default\s*\{)\n/;
|
|
3737
|
+
if (exportPattern.exec(content)) return content.replace(exportPattern, `$1\n ignore: ["${REQUIRED_IGNORE}"],\n`);
|
|
3883
3738
|
}
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3739
|
+
async function generateKnip(ctx) {
|
|
3740
|
+
const filePath = "knip.config.ts";
|
|
3741
|
+
const isMonorepo = ctx.config.structure === "monorepo";
|
|
3742
|
+
const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
3743
|
+
if (existingPath) {
|
|
3744
|
+
const content = ctx.read(existingPath);
|
|
3745
|
+
if (content) {
|
|
3746
|
+
const updated = ensureIgnorePattern(content, existingPath);
|
|
3747
|
+
if (updated) {
|
|
3748
|
+
ctx.write(existingPath, updated);
|
|
3749
|
+
return {
|
|
3750
|
+
filePath: existingPath,
|
|
3751
|
+
action: "updated",
|
|
3752
|
+
description: "Added \".claude/hooks/**\" to ignore"
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3899
3755
|
}
|
|
3756
|
+
return {
|
|
3757
|
+
filePath: existingPath,
|
|
3758
|
+
action: "skipped",
|
|
3759
|
+
description: existingPath === filePath ? "Already configured" : `Existing config found at ${existingPath}`
|
|
3760
|
+
};
|
|
3900
3761
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
filePath: "package.json",
|
|
3909
|
-
action: "updated",
|
|
3910
|
-
description: changes.join(", ")
|
|
3911
|
-
});
|
|
3762
|
+
const config = isMonorepo ? KNIP_CONFIG_MONOREPO : KNIP_CONFIG_SINGLE;
|
|
3763
|
+
ctx.write(filePath, config);
|
|
3764
|
+
return {
|
|
3765
|
+
filePath,
|
|
3766
|
+
action: "created",
|
|
3767
|
+
description: "Generated knip.config.ts for dead code analysis"
|
|
3768
|
+
};
|
|
3912
3769
|
}
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3770
|
+
//#endregion
|
|
3771
|
+
//#region src/generators/renovate.ts
|
|
3772
|
+
const SHARED_PRESET = "local>bensandee/tooling";
|
|
3773
|
+
/** Deprecated npm-based preset to migrate away from. */
|
|
3774
|
+
const LEGACY_PRESET = "@bensandee/config";
|
|
3775
|
+
/** All known renovate config file locations, in priority order. */
|
|
3776
|
+
const RENOVATE_CONFIG_PATHS = [
|
|
3777
|
+
"renovate.json",
|
|
3778
|
+
"renovate.json5",
|
|
3779
|
+
".renovaterc",
|
|
3780
|
+
".renovaterc.json",
|
|
3781
|
+
".github/renovate.json",
|
|
3782
|
+
".github/renovate.json5"
|
|
3783
|
+
];
|
|
3784
|
+
async function generateRenovate(ctx) {
|
|
3785
|
+
const defaultPath = "renovate.json";
|
|
3786
|
+
if (!ctx.config.setupRenovate) return {
|
|
3787
|
+
filePath: defaultPath,
|
|
3788
|
+
action: "skipped",
|
|
3789
|
+
description: "Renovate not requested"
|
|
3790
|
+
};
|
|
3791
|
+
const existingPath = RENOVATE_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
3921
3792
|
if (existingPath) {
|
|
3922
3793
|
const existing = ctx.read(existingPath);
|
|
3923
3794
|
if (existing) {
|
|
3924
|
-
const
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3795
|
+
const parsed = parseRenovateJson(existing);
|
|
3796
|
+
const existingExtends = parsed.extends ?? [];
|
|
3797
|
+
const legacyIndex = existingExtends.indexOf(LEGACY_PRESET);
|
|
3798
|
+
if (legacyIndex !== -1) {
|
|
3799
|
+
existingExtends[legacyIndex] = SHARED_PRESET;
|
|
3800
|
+
parsed.extends = existingExtends;
|
|
3801
|
+
ctx.write(existingPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
3802
|
+
return {
|
|
3928
3803
|
filePath: existingPath,
|
|
3929
3804
|
action: "updated",
|
|
3930
|
-
description:
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3805
|
+
description: `Migrated extends: ${LEGACY_PRESET} → ${SHARED_PRESET}`
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
if (!existingExtends.includes(SHARED_PRESET)) {
|
|
3809
|
+
existingExtends.unshift(SHARED_PRESET);
|
|
3810
|
+
parsed.extends = existingExtends;
|
|
3811
|
+
ctx.write(existingPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
3812
|
+
return {
|
|
3813
|
+
filePath: existingPath,
|
|
3814
|
+
action: "updated",
|
|
3815
|
+
description: `Added extends: ${SHARED_PRESET}`
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
return {
|
|
3933
3819
|
filePath: existingPath,
|
|
3934
3820
|
action: "skipped",
|
|
3935
|
-
description: "
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
filePath: existingPath,
|
|
3939
|
-
action: "skipped",
|
|
3940
|
-
description: "Could not read existing lefthook config"
|
|
3941
|
-
});
|
|
3942
|
-
return results;
|
|
3821
|
+
description: "Already extends shared config"
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3943
3824
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3825
|
+
const config = {
|
|
3826
|
+
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
|
3827
|
+
extends: [SHARED_PRESET]
|
|
3828
|
+
};
|
|
3829
|
+
ctx.write(defaultPath, JSON.stringify(config, null, 2) + "\n");
|
|
3830
|
+
return {
|
|
3831
|
+
filePath: defaultPath,
|
|
3832
|
+
action: "created",
|
|
3833
|
+
description: "Generated renovate.json extending shared config"
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
//#endregion
|
|
3837
|
+
//#region src/generators/pnpm-workspace.ts
|
|
3838
|
+
const WORKSPACE_YAML = `packages:
|
|
3839
|
+
- "packages/*"
|
|
3840
|
+
`;
|
|
3841
|
+
async function generatePnpmWorkspace(ctx) {
|
|
3842
|
+
const filePath = "pnpm-workspace.yaml";
|
|
3843
|
+
if (ctx.config.structure !== "monorepo") return {
|
|
3844
|
+
filePath,
|
|
3845
|
+
action: "skipped",
|
|
3846
|
+
description: "Not a monorepo"
|
|
3847
|
+
};
|
|
3848
|
+
if (ctx.exists(filePath)) return {
|
|
3849
|
+
filePath,
|
|
3850
|
+
action: "skipped",
|
|
3851
|
+
description: "pnpm-workspace.yaml already exists"
|
|
3852
|
+
};
|
|
3853
|
+
ctx.write(filePath, WORKSPACE_YAML);
|
|
3854
|
+
return {
|
|
3946
3855
|
filePath,
|
|
3947
3856
|
action: "created",
|
|
3948
|
-
description: "Generated
|
|
3949
|
-
}
|
|
3950
|
-
return results;
|
|
3857
|
+
description: "Generated pnpm-workspace.yaml"
|
|
3858
|
+
};
|
|
3951
3859
|
}
|
|
3952
3860
|
//#endregion
|
|
3953
|
-
//#region src/generators/
|
|
3954
|
-
const
|
|
3955
|
-
const
|
|
3956
|
-
|
|
3957
|
-
const
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3861
|
+
//#region src/generators/claude-hooks.ts
|
|
3862
|
+
const HOOK_PATH = ".claude/hooks/bst-hook.mjs";
|
|
3863
|
+
const GUIDE_PATH = ".claude/hooks/bst-hook-guide.md";
|
|
3864
|
+
function writeIfChanged(ctx, filePath, content, label) {
|
|
3865
|
+
const existing = ctx.read(filePath);
|
|
3866
|
+
if (existing === content) return {
|
|
3867
|
+
filePath,
|
|
3868
|
+
action: "skipped",
|
|
3869
|
+
description: `${label} is up to date`
|
|
3870
|
+
};
|
|
3871
|
+
ctx.write(filePath, content);
|
|
3872
|
+
return {
|
|
3873
|
+
filePath,
|
|
3874
|
+
action: existing ? "updated" : "created",
|
|
3875
|
+
description: existing ? `Updated ${label}` : `Generated ${label}`
|
|
3876
|
+
};
|
|
3963
3877
|
}
|
|
3964
|
-
function
|
|
3965
|
-
return JSON.stringify(settings, null, 2) + "\n";
|
|
3878
|
+
async function generateClaudeHooks(ctx) {
|
|
3879
|
+
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.7.3\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport * as fs from \"node:fs\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execFileSync } from \"node:child_process\";\nimport * as os from \"node:os\";\n\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/core.js\nvar _a$1;\nfunction $constructor(name, initializer, params) {\n function init(inst, def) {\n if (!inst._zod) Object.defineProperty(inst, \"_zod\", {\n value: {\n def,\n constr: _,\n traits: /* @__PURE__ */ new Set()\n },\n enumerable: false\n });\n if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n const proto = _.prototype;\n const keys = Object.keys(proto);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i];\n if (!(k in inst)) inst[k] = proto[k].bind(inst);\n }\n }\n const Parent = params?.Parent ?? Object;\n class Definition extends Parent {}\n Object.defineProperty(Definition, \"name\", { value: name });\n function _(def) {\n var _a;\n const inst = params?.Parent ? new Definition() : this;\n init(inst, def);\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n for (const fn of inst._zod.deferred) fn();\n return inst;\n }\n Object.defineProperty(_, \"init\", { value: init });\n Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {\n if (params?.Parent && inst instanceof params.Parent) return true;\n return inst?._zod?.traits?.has(name);\n } });\n Object.defineProperty(_, \"name\", { value: name });\n return _;\n}\nvar $ZodAsyncError = class extends Error {\n constructor() {\n super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);\n }\n};\nvar $ZodEncodeError = class extends Error {\n constructor(name) {\n super(`Encountered unidirectional transform during encode: ${name}`);\n this.name = \"ZodEncodeError\";\n }\n};\n(_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__zod_globalConfig = {});\nconst globalConfig = globalThis.__zod_globalConfig;\nfunction config(newConfig) {\n if (newConfig) Object.assign(globalConfig, newConfig);\n return globalConfig;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/util.js\nfunction getEnumValues(entries) {\n const numericValues = Object.values(entries).filter((v) => typeof v === \"number\");\n return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);\n}\nfunction jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nfunction cached(getter) {\n return { get value() {\n {\n const value = getter();\n Object.defineProperty(this, \"value\", { value });\n return value;\n }\n throw new Error(\"cached value already set\");\n } };\n}\nfunction nullish(input) {\n return input === null || input === void 0;\n}\nfunction cleanRegex(source) {\n const start = source.startsWith(\"^\") ? 1 : 0;\n const end = source.endsWith(\"$\") ? source.length - 1 : source.length;\n return source.slice(start, end);\n}\nconst EVALUATING = /* @__PURE__ */ Symbol(\"evaluating\");\nfunction defineLazy(object, key, getter) {\n let value = void 0;\n Object.defineProperty(object, key, {\n get() {\n if (value === EVALUATING) return;\n if (value === void 0) {\n value = EVALUATING;\n value = getter();\n }\n return value;\n },\n set(v) {\n Object.defineProperty(object, key, { value: v });\n },\n configurable: true\n });\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\n });\n}\nfunction mergeDefs(...defs) {\n const mergedDescriptors = {};\n for (const def of defs) Object.assign(mergedDescriptors, Object.getOwnPropertyDescriptors(def));\n return Object.defineProperties({}, mergedDescriptors);\n}\nfunction esc(str) {\n return JSON.stringify(str);\n}\nfunction slugify(input) {\n return input.toLowerCase().trim().replace(/[^\\w\\s-]/g, \"\").replace(/[\\s_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nconst allowsEval = /* @__PURE__ */ cached(() => {\n if (globalConfig.jitless) return false;\n if (typeof navigator !== \"undefined\" && navigator?.userAgent?.includes(\"Cloudflare\")) return false;\n try {\n new Function(\"\");\n return true;\n } catch (_) {\n return false;\n }\n});\nfunction isPlainObject(o) {\n if (isObject(o) === false) return false;\n const ctor = o.constructor;\n if (ctor === void 0) return true;\n if (typeof ctor !== \"function\") return true;\n const prot = ctor.prototype;\n if (isObject(prot) === false) return false;\n if (Object.prototype.hasOwnProperty.call(prot, \"isPrototypeOf\") === false) return false;\n return true;\n}\nfunction shallowClone(o) {\n if (isPlainObject(o)) return { ...o };\n if (Array.isArray(o)) return [...o];\n if (o instanceof Map) return new Map(o);\n if (o instanceof Set) return new Set(o);\n return o;\n}\nconst propertyKeyTypes = /* @__PURE__ */ new Set([\n \"string\",\n \"number\",\n \"symbol\"\n]);\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\nfunction clone(inst, def, params) {\n const cl = new inst._zod.constr(def ?? inst._zod.def);\n if (!def || params?.parent) cl._zod.parent = inst;\n return cl;\n}\nfunction normalizeParams(_params) {\n const params = _params;\n if (!params) return {};\n if (typeof params === \"string\") return { error: () => params };\n if (params?.message !== void 0) {\n if (params?.error !== void 0) throw new Error(\"Cannot specify both `message` and `error` params\");\n params.error = params.message;\n }\n delete params.message;\n if (typeof params.error === \"string\") return {\n ...params,\n error: () => params.error\n };\n return params;\n}\nfunction optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin === \"optional\" && shape[k]._zod.optout === \"optional\";\n });\n}\nconst NUMBER_FORMAT_RANGES = {\n safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],\n int32: [-2147483648, 2147483647],\n uint32: [0, 4294967295],\n float32: [-34028234663852886e22, 34028234663852886e22],\n float64: [-Number.MAX_VALUE, Number.MAX_VALUE]\n};\nfunction pick(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".pick() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = {};\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n newShape[key] = currDef.shape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\n}\nfunction omit(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".omit() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = { ...schema._zod.def.shape };\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n delete newShape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\n}\nfunction extend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to extend: expected a plain object\");\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) {\n const existingShape = schema._zod.def.shape;\n for (const key in shape) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error(\"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.\");\n }\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction safeExtend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to safeExtend: expected a plain object\");\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction merge(a, b) {\n if (a._zod.def.checks?.length) throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n return clone(a, mergeDefs(a._zod.def, {\n get shape() {\n const _shape = {\n ...a._zod.def.shape,\n ...b._zod.def.shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n },\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: b._zod.def.checks ?? []\n }));\n}\nfunction partial(Class, schema, mask) {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(\".partial() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in oldShape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n }\n else for (const key in oldShape) shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n assignProp(this, \"shape\", shape);\n return shape;\n },\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n }\n else for (const key in oldShape) shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n assignProp(this, \"shape\", shape);\n return shape;\n } }));\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction explicitlyAborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction finalizeIssue(iss, ctx, config) {\n const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;\n rest.path ?? (rest.path = []);\n rest.message = message;\n if (ctx?.reportInput) rest.input = _input;\n return rest;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\n}\nfunction issue(...args) {\n const [iss, input, inst] = args;\n if (typeof iss === \"string\") return {\n message: iss,\n code: \"custom\",\n input,\n inst\n };\n return { ...iss };\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/errors.js\nconst initializer$1 = (inst, def) => {\n inst.name = \"$ZodError\";\n Object.defineProperty(inst, \"_zod\", {\n value: inst._zod,\n enumerable: false\n });\n Object.defineProperty(inst, \"issues\", {\n value: def,\n enumerable: false\n });\n inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);\n Object.defineProperty(inst, \"toString\", {\n value: () => inst.message,\n enumerable: false\n });\n};\nconst $ZodError = $constructor(\"$ZodError\", initializer$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, { Parent: Error });\nfunction flattenError(error, mapper = (issue) => issue.message) {\n const fieldErrors = {};\n const formErrors = [];\n for (const sub of error.issues) if (sub.path.length > 0) {\n fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];\n fieldErrors[sub.path[0]].push(mapper(sub));\n } else formErrors.push(mapper(sub));\n return {\n formErrors,\n fieldErrors\n };\n}\nfunction formatError(error, mapper = (issue) => issue.message) {\n const fieldErrors = { _errors: [] };\n const processError = (error, path = []) => {\n for (const issue of error.issues) if (issue.code === \"invalid_union\" && issue.errors.length) issue.errors.map((issues) => processError({ issues }, [...path, ...issue.path]));\n else if (issue.code === \"invalid_key\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else if (issue.code === \"invalid_element\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else {\n const fullpath = [...path, ...issue.path];\n if (fullpath.length === 0) fieldErrors._errors.push(mapper(issue));\n else {\n let curr = fieldErrors;\n let i = 0;\n while (i < fullpath.length) {\n const el = fullpath[i];\n if (!(i === fullpath.length - 1)) curr[el] = curr[el] || { _errors: [] };\n else {\n curr[el] = curr[el] || { _errors: [] };\n curr[el]._errors.push(mapper(issue));\n }\n curr = curr[el];\n i++;\n }\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n if (result.issues.length) {\n const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, _params?.callee);\n throw e;\n }\n return result.value;\n};\nconst _parseAsync = (_Err) => async (schema, value, _ctx, params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n if (result.issues.length) {\n const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, params?.callee);\n throw e;\n }\n return result.value;\n};\nconst _safeParse = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length ? {\n success: false,\n error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);\nconst _safeParseAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n return result.issues.length ? {\n success: false,\n error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);\nconst _encode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parse(_Err)(schema, value, ctx);\n};\nconst _decode = (_Err) => (schema, value, _ctx) => {\n return _parse(_Err)(schema, value, _ctx);\n};\nconst _encodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parseAsync(_Err)(schema, value, ctx);\n};\nconst _decodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _parseAsync(_Err)(schema, value, _ctx);\n};\nconst _safeEncode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParse(_Err)(schema, value, ctx);\n};\nconst _safeDecode = (_Err) => (schema, value, _ctx) => {\n return _safeParse(_Err)(schema, value, _ctx);\n};\nconst _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParseAsync(_Err)(schema, value, ctx);\n};\nconst _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _safeParseAsync(_Err)(schema, value, _ctx);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/regexes.js\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst cuid = /^[cC][0-9a-z]{6,}$/;\nconst cuid2 = /^[0-9a-z]+$/;\nconst ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration$1 = /^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$/;\n/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */\nconst guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n/** Returns a regex for validating an RFC 9562/4122 UUID.\n*\n* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */\nconst uuid = (version) => {\n if (!version) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\n return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);\n};\n/** Practical email validation */\nconst email = /^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/;\nconst _emoji$1 = `^(\\\\p{Extended_Pictographic}|\\\\p{Emoji_Component})+$`;\nfunction emoji() {\n return new RegExp(_emoji$1, \"u\");\n}\nconst ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;\nconst ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;\nconst cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$/;\nconst cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;\nconst base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;\nconst base64url = /^[A-Za-z0-9_-]*$/;\nconst httpProtocol = /^https?$/;\nconst e164 = /^\\+[1-9]\\d{6,14}$/;\nconst dateSource = `(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))`;\nconst date$1 = /* @__PURE__ */ new RegExp(`^${dateSource}$`);\nfunction timeSource(args) {\n const hhmm = `(?:[01]\\\\d|2[0-3]):[0-5]\\\\d`;\n return typeof args.precision === \"number\" ? args.precision === -1 ? `${hhmm}` : args.precision === 0 ? `${hhmm}:[0-5]\\\\d` : `${hhmm}:[0-5]\\\\d\\\\.\\\\d{${args.precision}}` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time$1(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime$1(args) {\n const time = timeSource({ precision: args.precision });\n const opts = [\"Z\"];\n if (args.local) opts.push(\"\");\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const timeRegex = `${time}(?:${opts.join(\"|\")})`;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst string$1 = (params) => {\n const regex = params ? `[\\\\s\\\\S]{${params?.minimum ?? 0},${params?.maximum ?? \"\"}}` : `[\\\\s\\\\S]*`;\n return new RegExp(`^${regex}$`);\n};\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /* @__PURE__ */ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst $ZodCheckMaxLength = /* @__PURE__ */ $constructor(\"$ZodCheckMaxLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY;\n if (def.maximum < curr) inst._zod.bag.maximum = def.maximum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length <= def.maximum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_big\",\n maximum: def.maximum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckMinLength = /* @__PURE__ */ $constructor(\"$ZodCheckMinLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY;\n if (def.minimum > curr) inst._zod.bag.minimum = def.minimum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length >= def.minimum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_small\",\n minimum: def.minimum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLengthEquals = /* @__PURE__ */ $constructor(\"$ZodCheckLengthEquals\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.minimum = def.length;\n bag.maximum = def.length;\n bag.length = def.length;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n const length = input.length;\n if (length === def.length) return;\n const origin = getLengthableOrigin(input);\n const tooBig = length > def.length;\n payload.issues.push({\n origin,\n ...tooBig ? {\n code: \"too_big\",\n maximum: def.length\n } : {\n code: \"too_small\",\n minimum: def.length\n },\n inclusive: true,\n exact: true,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStringFormat = /* @__PURE__ */ $constructor(\"$ZodCheckStringFormat\", (inst, def) => {\n var _a, _b;\n $ZodCheck.init(inst, def);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.format = def.format;\n if (def.pattern) {\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(def.pattern);\n }\n });\n if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: def.format,\n input: payload.value,\n ...def.pattern ? { pattern: def.pattern.toString() } : {},\n inst,\n continue: !def.abort\n });\n });\n else (_b = inst._zod).check ?? (_b.check = () => {});\n});\nconst $ZodCheckRegex = /* @__PURE__ */ $constructor(\"$ZodCheckRegex\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"regex\",\n input: payload.value,\n pattern: def.pattern.toString(),\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLowerCase = /* @__PURE__ */ $constructor(\"$ZodCheckLowerCase\", (inst, def) => {\n def.pattern ?? (def.pattern = lowercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckUpperCase = /* @__PURE__ */ $constructor(\"$ZodCheckUpperCase\", (inst, def) => {\n def.pattern ?? (def.pattern = uppercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckIncludes = /* @__PURE__ */ $constructor(\"$ZodCheckIncludes\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const escapedRegex = escapeRegex(def.includes);\n const pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);\n def.pattern = pattern;\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.includes(def.includes, def.position)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"includes\",\n includes: def.includes,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStartsWith = /* @__PURE__ */ $constructor(\"$ZodCheckStartsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.startsWith(def.prefix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"starts_with\",\n prefix: def.prefix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckEndsWith = /* @__PURE__ */ $constructor(\"$ZodCheckEndsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\n inst._zod.check = (payload) => {\n if (payload.value.endsWith(def.suffix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"ends_with\",\n suffix: def.suffix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckOverwrite = /* @__PURE__ */ $constructor(\"$ZodCheckOverwrite\", (inst, def) => {\n $ZodCheck.init(inst, def);\n inst._zod.check = (payload) => {\n payload.value = def.tx(payload.value);\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = []) {\n this.content = [];\n this.indent = 0;\n if (this) this.args = args;\n }\n indented(fn) {\n this.indent += 1;\n fn(this);\n this.indent -= 1;\n }\n write(arg) {\n if (typeof arg === \"function\") {\n arg(this, { execution: \"sync\" });\n arg(this, { execution: \"async\" });\n return;\n }\n const lines = arg.split(\"\\n\").filter((x) => x);\n const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));\n const dedented = lines.map((x) => x.slice(minIndent)).map((x) => \" \".repeat(this.indent * 2) + x);\n for (const line of dedented) this.content.push(line);\n }\n compile() {\n const F = Function;\n const args = this?.args;\n const lines = [...(this?.content ?? [``]).map((x) => ` ${x}`)];\n return new F(...args, lines.join(\"\\n\"));\n }\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 4,\n patch: 1\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/schemas.js\nconst $ZodType = /* @__PURE__ */ $constructor(\"$ZodType\", (inst, def) => {\n var _a;\n inst ?? (inst = {});\n inst._zod.def = def;\n inst._zod.bag = inst._zod.bag || {};\n inst._zod.version = version;\n const checks = [...inst._zod.def.checks ?? []];\n if (inst._zod.traits.has(\"$ZodCheck\")) checks.unshift(inst);\n for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);\n if (checks.length === 0) {\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred?.push(() => {\n inst._zod.run = inst._zod.parse;\n });\n } else {\n const runChecks = (payload, checks, ctx) => {\n let isAborted = aborted(payload);\n let asyncResult;\n for (const ch of checks) {\n if (ch._zod.def.when) {\n if (explicitlyAborted(payload)) continue;\n if (!ch._zod.def.when(payload)) continue;\n } else if (isAborted) continue;\n const currLen = payload.issues.length;\n const _ = ch._zod.check(payload);\n if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();\n if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {\n await _;\n if (payload.issues.length === currLen) return;\n if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\n if (!isAborted) isAborted = aborted(payload, currLen);\n }\n }\n if (asyncResult) return asyncResult.then(() => {\n return payload;\n });\n return payload;\n };\n const handleCanaryResult = (canary, payload, ctx) => {\n if (aborted(canary)) {\n canary.aborted = true;\n return canary;\n }\n const checkResult = runChecks(payload, checks, ctx);\n if (checkResult instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));\n }\n return inst._zod.parse(checkResult, ctx);\n };\n inst._zod.run = (payload, ctx) => {\n if (ctx.skipChecks) return inst._zod.parse(payload, ctx);\n if (ctx.direction === \"backward\") {\n const canary = inst._zod.parse({\n value: payload.value,\n issues: []\n }, {\n ...ctx,\n skipChecks: true\n });\n if (canary instanceof Promise) return canary.then((canary) => {\n return handleCanaryResult(canary, payload, ctx);\n });\n return handleCanaryResult(canary, payload, ctx);\n }\n const result = inst._zod.parse(payload, ctx);\n if (result instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return result.then((result) => runChecks(result, checks, ctx));\n }\n return runChecks(result, checks, ctx);\n };\n }\n defineLazy(inst, \"~standard\", () => ({\n validate: (value) => {\n try {\n const r = safeParse$1(inst, value);\n return r.success ? { value: r.data } : { issues: r.error?.issues };\n } catch (_) {\n return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });\n }\n },\n vendor: \"zod\",\n version: 1\n }));\n});\nconst $ZodString = /* @__PURE__ */ $constructor(\"$ZodString\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$1(inst._zod.bag);\n inst._zod.parse = (payload, _) => {\n if (def.coerce) try {\n payload.value = String(payload.value);\n } catch (_) {}\n if (typeof payload.value === \"string\") return payload;\n payload.issues.push({\n expected: \"string\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nconst $ZodStringFormat = /* @__PURE__ */ $constructor(\"$ZodStringFormat\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n $ZodString.init(inst, def);\n});\nconst $ZodGUID = /* @__PURE__ */ $constructor(\"$ZodGUID\", (inst, def) => {\n def.pattern ?? (def.pattern = guid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodUUID = /* @__PURE__ */ $constructor(\"$ZodUUID\", (inst, def) => {\n if (def.version) {\n const v = {\n v1: 1,\n v2: 2,\n v3: 3,\n v4: 4,\n v5: 5,\n v6: 6,\n v7: 7,\n v8: 8\n }[def.version];\n if (v === void 0) throw new Error(`Invalid UUID version: \"${def.version}\"`);\n def.pattern ?? (def.pattern = uuid(v));\n } else def.pattern ?? (def.pattern = uuid());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodEmail = /* @__PURE__ */ $constructor(\"$ZodEmail\", (inst, def) => {\n def.pattern ?? (def.pattern = email);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodURL = /* @__PURE__ */ $constructor(\"$ZodURL\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n try {\n const trimmed = payload.value.trim();\n if (!def.normalize && def.protocol?.source === httpProtocol.source) {\n if (!/^https?:\\/\\//i.test(trimmed)) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid URL format\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n return;\n }\n }\n const url = new URL(trimmed);\n if (def.hostname) {\n def.hostname.lastIndex = 0;\n if (!def.hostname.test(url.hostname)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid hostname\",\n pattern: def.hostname.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n if (def.protocol) {\n def.protocol.lastIndex = 0;\n if (!def.protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.protocol)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid protocol\",\n pattern: def.protocol.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n if (def.normalize) payload.value = url.href;\n else payload.value = trimmed;\n return;\n } catch (_) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodEmoji = /* @__PURE__ */ $constructor(\"$ZodEmoji\", (inst, def) => {\n def.pattern ?? (def.pattern = emoji());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodNanoID = /* @__PURE__ */ $constructor(\"$ZodNanoID\", (inst, def) => {\n def.pattern ?? (def.pattern = nanoid);\n $ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link $ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst $ZodCUID = /* @__PURE__ */ $constructor(\"$ZodCUID\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCUID2 = /* @__PURE__ */ $constructor(\"$ZodCUID2\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid2);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodULID = /* @__PURE__ */ $constructor(\"$ZodULID\", (inst, def) => {\n def.pattern ?? (def.pattern = ulid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodXID = /* @__PURE__ */ $constructor(\"$ZodXID\", (inst, def) => {\n def.pattern ?? (def.pattern = xid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodKSUID = /* @__PURE__ */ $constructor(\"$ZodKSUID\", (inst, def) => {\n def.pattern ?? (def.pattern = ksuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODateTime = /* @__PURE__ */ $constructor(\"$ZodISODateTime\", (inst, def) => {\n def.pattern ?? (def.pattern = datetime$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /* @__PURE__ */ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /* @__PURE__ */ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /* @__PURE__ */ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodIPv4 = /* @__PURE__ */ $constructor(\"$ZodIPv4\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv4);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv4`;\n});\nconst $ZodIPv6 = /* @__PURE__ */ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv6`;\n inst._zod.check = (payload) => {\n try {\n new URL(`http://[${payload.value}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodCIDRv4 = /* @__PURE__ */ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCIDRv6 = /* @__PURE__ */ $constructor(\"$ZodCIDRv6\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n const parts = payload.value.split(\"/\");\n try {\n if (parts.length !== 2) throw new Error();\n const [address, prefix] = parts;\n if (!prefix) throw new Error();\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) throw new Error();\n if (prefixNum < 0 || prefixNum > 128) throw new Error();\n new URL(`http://[${address}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nfunction isValidBase64(data) {\n if (data === \"\") return true;\n if (/\\s/.test(data)) return false;\n if (data.length % 4 !== 0) return false;\n try {\n atob(data);\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodBase64 = /* @__PURE__ */ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64\";\n inst._zod.check = (payload) => {\n if (isValidBase64(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nfunction isValidBase64URL(data) {\n if (!base64url.test(data)) return false;\n const base64 = data.replace(/[-_]/g, (c) => c === \"-\" ? \"+\" : \"/\");\n return isValidBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\"));\n}\nconst $ZodBase64URL = /* @__PURE__ */ $constructor(\"$ZodBase64URL\", (inst, def) => {\n def.pattern ?? (def.pattern = base64url);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64url\";\n inst._zod.check = (payload) => {\n if (isValidBase64URL(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodE164 = /* @__PURE__ */ $constructor(\"$ZodE164\", (inst, def) => {\n def.pattern ?? (def.pattern = e164);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidJWT(token, algorithm = null) {\n try {\n const tokensParts = token.split(\".\");\n if (tokensParts.length !== 3) return false;\n const [header] = tokensParts;\n if (!header) return false;\n const parsedHeader = JSON.parse(atob(header));\n if (\"typ\" in parsedHeader && parsedHeader?.typ !== \"JWT\") return false;\n if (!parsedHeader.alg) return false;\n if (algorithm && (!(\"alg\" in parsedHeader) || parsedHeader.alg !== algorithm)) return false;\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodJWT = /* @__PURE__ */ $constructor(\"$ZodJWT\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidJWT(payload.value, def.alg)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"jwt\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodUnknown = /* @__PURE__ */ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nconst $ZodNever = /* @__PURE__ */ $constructor(\"$ZodNever\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _ctx) => {\n payload.issues.push({\n expected: \"never\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nfunction handleArrayResult(result, final, index) {\n if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));\n final.value[index] = result.value;\n}\nconst $ZodArray = /* @__PURE__ */ $constructor(\"$ZodArray\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!Array.isArray(input)) {\n payload.issues.push({\n expected: \"array\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = Array(input.length);\n const proms = [];\n for (let i = 0; i < input.length; i++) {\n const item = input[i];\n const result = def.element._zod.run({\n value: item,\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i)));\n else handleArrayResult(result, payload, i);\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) {\n const isPresent = key in input;\n if (result.issues.length) {\n if (isOptionalIn && isOptionalOut && !isPresent) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (!isPresent && !isOptionalIn) {\n if (!result.issues.length) final.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: void 0,\n path: [key]\n });\n return;\n }\n if (result.value === void 0) {\n if (isPresent) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${k}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n keys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const isOptionalIn = _catchall.optin === \"optional\";\n const isOptionalOut = _catchall.optout === \"optional\";\n for (const key in input) {\n if (key === \"__proto__\") continue;\n if (keySet.has(key)) continue;\n if (t === \"never\") {\n unrecognized.push(key);\n continue;\n }\n const r = _catchall.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst\n });\n if (!proms.length) return payload;\n return Promise.all(proms).then(() => {\n return payload;\n });\n}\nconst $ZodObject = /* @__PURE__ */ $constructor(\"$ZodObject\", (inst, def) => {\n $ZodType.init(inst, def);\n if (!Object.getOwnPropertyDescriptor(def, \"shape\")?.get) {\n const sh = def.shape;\n Object.defineProperty(def, \"shape\", { get: () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n return newSh;\n } });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazy(inst._zod, \"propValues\", () => {\n const shape = def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n }\n }\n return propValues;\n });\n const isObject$1 = isObject;\n const catchall = def.catchall;\n let value;\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$1(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = {};\n const proms = [];\n const shape = value.shape;\n for (const key of value.keys) {\n const el = shape[key];\n const isOptionalIn = el._zod.optin === \"optional\";\n const isOptionalOut = el._zod.optout === \"optional\";\n const r = el._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);\n };\n});\nconst $ZodObjectJIT = /* @__PURE__ */ $constructor(\"$ZodObjectJIT\", (inst, def) => {\n $ZodObject.init(inst, def);\n const superParse = inst._zod.parse;\n const _normalized = cached(() => normalizeDef(def));\n const generateFastpass = (shape) => {\n const doc = new Doc([\n \"shape\",\n \"payload\",\n \"ctx\"\n ]);\n const normalized = _normalized.value;\n const parseStr = (key) => {\n const k = esc(key);\n return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n };\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.keys) ids[key] = `key_${counter++}`;\n doc.write(`const newResult = {};`);\n for (const key of normalized.keys) {\n const id = ids[key];\n const k = esc(key);\n const schema = shape[key];\n const isOptionalIn = schema?._zod?.optin === \"optional\";\n const isOptionalOut = schema?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(key)};`);\n if (isOptionalIn && isOptionalOut) doc.write(`\n if (${id}.issues.length) {\n if (${k} in input) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n else if (!isOptionalIn) doc.write(`\n const ${id}_present = ${k} in input;\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n if (!${id}_present && !${id}.issues.length) {\n payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: undefined,\n path: [${k}]\n });\n }\n\n if (${id}_present) {\n if (${id}.value === undefined) {\n newResult[${k}] = undefined;\n } else {\n newResult[${k}] = ${id}.value;\n }\n }\n\n `);\n else doc.write(`\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n const fn = doc.compile();\n return (payload, ctx) => fn(shape, payload, ctx);\n };\n let fastpass;\n const isObject$2 = isObject;\n const jit = !globalConfig.jitless;\n const allowsEval$1 = allowsEval;\n const fastEnabled = jit && allowsEval$1.value;\n const catchall = def.catchall;\n let value;\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$2(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n if (jit && fastEnabled && ctx?.async === false && ctx.jitless !== true) {\n if (!fastpass) fastpass = generateFastpass(def.shape);\n payload = fastpass(payload, ctx);\n if (!catchall) return payload;\n return handleCatchall([], input, payload, ctx, value, inst);\n }\n return superParse(payload, ctx);\n };\n});\nfunction handleUnionResults(results, final, inst, ctx) {\n for (const result of results) if (result.issues.length === 0) {\n final.value = result.value;\n return final;\n }\n const nonaborted = results.filter((r) => !aborted(r));\n if (nonaborted.length === 1) {\n final.value = nonaborted[0].value;\n return nonaborted[0];\n }\n final.issues.push({\n code: \"invalid_union\",\n input: final.value,\n inst,\n errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n });\n return final;\n}\nconst $ZodUnion = /* @__PURE__ */ $constructor(\"$ZodUnion\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.options.some((o) => o._zod.optin === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"optout\", () => def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"values\", () => {\n if (def.options.every((o) => o._zod.values)) return new Set(def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazy(inst._zod, \"pattern\", () => {\n if (def.options.every((o) => o._zod.pattern)) {\n const patterns = def.options.map((o) => o._zod.pattern);\n return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join(\"|\")})$`);\n }\n });\n const first = def.options.length === 1 ? def.options[0]._zod.run : null;\n inst._zod.parse = (payload, ctx) => {\n if (first) return first(payload, ctx);\n let async = false;\n const results = [];\n for (const option of def.options) {\n const result = option._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) {\n results.push(result);\n async = true;\n } else {\n if (result.issues.length === 0) return result;\n results.push(result);\n }\n }\n if (!async) return handleUnionResults(results, payload, inst, ctx);\n return Promise.all(results).then((results) => {\n return handleUnionResults(results, payload, inst, ctx);\n });\n };\n});\nconst $ZodIntersection = /* @__PURE__ */ $constructor(\"$ZodIntersection\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n const left = def.left._zod.run({\n value: input,\n issues: []\n }, ctx);\n const right = def.right._zod.run({\n value: input,\n issues: []\n }, ctx);\n if (left instanceof Promise || right instanceof Promise) return Promise.all([left, right]).then(([left, right]) => {\n return handleIntersectionResults(payload, left, right);\n });\n return handleIntersectionResults(payload, left, right);\n };\n});\nfunction mergeValues(a, b) {\n if (a === b) return {\n valid: true,\n data: a\n };\n if (a instanceof Date && b instanceof Date && +a === +b) return {\n valid: true,\n data: a\n };\n if (isPlainObject(a) && isPlainObject(b)) {\n const bKeys = Object.keys(b);\n const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);\n const newObj = {\n ...a,\n ...b\n };\n for (const key of sharedKeys) {\n const sharedValue = mergeValues(a[key], b[key]);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [key, ...sharedValue.mergeErrorPath]\n };\n newObj[key] = sharedValue.data;\n }\n return {\n valid: true,\n data: newObj\n };\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return {\n valid: false,\n mergeErrorPath: []\n };\n const newArray = [];\n for (let index = 0; index < a.length; index++) {\n const itemA = a[index];\n const itemB = b[index];\n const sharedValue = mergeValues(itemA, itemB);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [index, ...sharedValue.mergeErrorPath]\n };\n newArray.push(sharedValue.data);\n }\n return {\n valid: true,\n data: newArray\n };\n }\n return {\n valid: false,\n mergeErrorPath: []\n };\n}\nfunction handleIntersectionResults(result, left, right) {\n const unrecKeys = /* @__PURE__ */ new Map();\n let unrecIssue;\n for (const iss of left.issues) if (iss.code === \"unrecognized_keys\") {\n unrecIssue ?? (unrecIssue = iss);\n for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).l = true;\n }\n } else result.issues.push(iss);\n for (const iss of right.issues) if (iss.code === \"unrecognized_keys\") for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).r = true;\n }\n else result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length && unrecIssue) result.issues.push({\n ...unrecIssue,\n keys: bothKeys\n });\n if (aborted(result)) return result;\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n result.value = merged.data;\n return result;\n}\nconst $ZodRecord = /* @__PURE__ */ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!isPlainObject(input)) {\n payload.issues.push({\n expected: \"record\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n const proms = [];\n const values = def.keyType._zod.values;\n if (values) {\n payload.value = {};\n const recordKeys = /* @__PURE__ */ new Set();\n for (const key of values) if (typeof key === \"string\" || typeof key === \"number\" || typeof key === \"symbol\") {\n recordKeys.add(typeof key === \"number\" ? key.toString() : key);\n const keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (keyResult.issues.length) {\n payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const outKey = keyResult.value;\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }\n }\n let unrecognized;\n for (const key in input) if (!recordKeys.has(key)) {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized\n });\n } else {\n payload.value = {};\n for (const key of Reflect.ownKeys(input)) {\n if (key === \"__proto__\") continue;\n if (!Object.prototype.propertyIsEnumerable.call(input, key)) continue;\n let keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (typeof key === \"string\" && number.test(key) && keyResult.issues.length) {\n const retryResult = def.keyType._zod.run({\n value: Number(key),\n issues: []\n }, ctx);\n if (retryResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (retryResult.issues.length === 0) keyResult = retryResult;\n }\n if (keyResult.issues.length) {\n if (def.mode === \"loose\") payload.value[key] = input[key];\n else payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }\n }\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nconst $ZodEnum = /* @__PURE__ */ $constructor(\"$ZodEnum\", (inst, def) => {\n $ZodType.init(inst, def);\n const values = getEnumValues(def.entries);\n const valuesSet = new Set(values);\n inst._zod.values = valuesSet;\n inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === \"string\" ? escapeRegex(o) : o.toString()).join(\"|\")})$`);\n inst._zod.parse = (payload, _ctx) => {\n const input = payload.value;\n if (valuesSet.has(input)) return payload;\n payload.issues.push({\n code: \"invalid_value\",\n values,\n input,\n inst\n });\n return payload;\n };\n});\nconst $ZodTransform = /* @__PURE__ */ $constructor(\"$ZodTransform\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n const _out = def.transform(payload.value, payload);\n if (ctx.async) return (_out instanceof Promise ? _out : Promise.resolve(_out)).then((output) => {\n payload.value = output;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\n return payload;\n };\n});\nfunction handleOptionalResult(result, input) {\n if (result.issues.length && input === void 0) return {\n issues: [],\n value: void 0\n };\n return result;\n}\nconst $ZodOptional = /* @__PURE__ */ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n inst._zod.optout = \"optional\";\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;\n });\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (def.innerType._zod.optin === \"optional\") {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, payload.value));\n return handleOptionalResult(result, payload.value);\n }\n if (payload.value === void 0) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodExactOptional = /* @__PURE__ */ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"pattern\", () => def.innerType._zod.pattern);\n inst._zod.parse = (payload, ctx) => {\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNullable = /* @__PURE__ */ $constructor(\"$ZodNullable\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === null) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodDefault = /* @__PURE__ */ $constructor(\"$ZodDefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) {\n payload.value = def.defaultValue;\n /**\n * $ZodDefault returns the default value immediately in forward direction.\n * It doesn't pass the default value into the validator (\"prefault\"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a \"prefault\" for the pipe. */\n return payload;\n }\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleDefaultResult(result, def));\n return handleDefaultResult(result, def);\n };\n});\nfunction handleDefaultResult(payload, def) {\n if (payload.value === void 0) payload.value = def.defaultValue;\n return payload;\n}\nconst $ZodPrefault = /* @__PURE__ */ $constructor(\"$ZodPrefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) payload.value = def.defaultValue;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNonOptional = /* @__PURE__ */ $constructor(\"$ZodNonOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => {\n const v = def.innerType._zod.values;\n return v ? new Set([...v].filter((x) => x !== void 0)) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleNonOptionalResult(result, inst));\n return handleNonOptionalResult(result, inst);\n };\n});\nfunction handleNonOptionalResult(payload, inst) {\n if (!payload.issues.length && payload.value === void 0) payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: payload.value,\n inst\n });\n return payload;\n}\nconst $ZodCatch = /* @__PURE__ */ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => {\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n }\n return payload;\n });\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n }\n return payload;\n };\n});\nconst $ZodPipe = /* @__PURE__ */ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.in._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.in._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.out._zod.optout);\n defineLazy(inst._zod, \"propValues\", () => def.in._zod.propValues);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") {\n const right = def.out._zod.run(payload, ctx);\n if (right instanceof Promise) return right.then((right) => handlePipeResult(right, def.in, ctx));\n return handlePipeResult(right, def.in, ctx);\n }\n const left = def.in._zod.run(payload, ctx);\n if (left instanceof Promise) return left.then((left) => handlePipeResult(left, def.out, ctx));\n return handlePipeResult(left, def.out, ctx);\n };\n});\nfunction handlePipeResult(left, next, ctx) {\n if (left.issues.length) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues\n }, ctx);\n}\nconst $ZodReadonly = /* @__PURE__ */ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"propValues\", () => def.innerType._zod.propValues);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.innerType?._zod?.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType?._zod?.optout);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then(handleReadonlyResult);\n return handleReadonlyResult(result);\n };\n});\nfunction handleReadonlyResult(payload) {\n payload.value = Object.freeze(payload.value);\n return payload;\n}\nconst $ZodCustom = /* @__PURE__ */ $constructor(\"$ZodCustom\", (inst, def) => {\n $ZodCheck.init(inst, def);\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _) => {\n return payload;\n };\n inst._zod.check = (payload) => {\n const input = payload.value;\n const r = def.fn(input);\n if (r instanceof Promise) return r.then((r) => handleRefineResult(r, payload, input, inst));\n handleRefineResult(r, payload, input, inst);\n };\n});\nfunction handleRefineResult(result, payload, input, inst) {\n if (!result) {\n const _iss = {\n code: \"custom\",\n input,\n inst,\n path: [...inst._zod.def.path ?? []],\n continue: !inst._zod.def.abort\n };\n if (inst._zod.def.params) _iss.params = inst._zod.def.params;\n payload.issues.push(issue(_iss));\n }\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/registries.js\nvar _a;\nvar $ZodRegistry = class {\n constructor() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n }\n add(schema, ..._meta) {\n const meta = _meta[0];\n this._map.set(schema, meta);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.set(meta.id, schema);\n return this;\n }\n clear() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n return this;\n }\n remove(schema) {\n const meta = this._map.get(schema);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.delete(meta.id);\n this._map.delete(schema);\n return this;\n }\n get(schema) {\n const p = schema._zod.parent;\n if (p) {\n const pm = { ...this.get(p) ?? {} };\n delete pm.id;\n const f = {\n ...pm,\n ...this._map.get(schema)\n };\n return Object.keys(f).length ? f : void 0;\n }\n return this._map.get(schema);\n }\n has(schema) {\n return this._map.has(schema);\n }\n};\nfunction registry() {\n return new $ZodRegistry();\n}\n(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());\nconst globalRegistry = globalThis.__zod_globalRegistry;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/api.js\n/* @__NO_SIDE_EFFECTS__ */\nfunction _string(Class, params) {\n return new Class({\n type: \"string\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _email(Class, params) {\n return new Class({\n type: \"string\",\n format: \"email\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _guid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"guid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v4\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v6\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uuidv7(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v7\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _emoji(Class, params) {\n return new Class({\n type: \"string\",\n format: \"emoji\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _nanoid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"nanoid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link _cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cuid2(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid2\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ulid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ulid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _xid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"xid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ksuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ksuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ipv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _ipv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cidrv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _cidrv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _base64(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _base64url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _e164(Class, params) {\n return new Class({\n type: \"string\",\n format: \"e164\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _jwt(Class, params) {\n return new Class({\n type: \"string\",\n format: \"jwt\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDateTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"datetime\",\n check: \"string_format\",\n offset: false,\n local: false,\n precision: null,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDate(Class, params) {\n return new Class({\n type: \"string\",\n format: \"date\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"time\",\n check: \"string_format\",\n precision: null,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _isoDuration(Class, params) {\n return new Class({\n type: \"string\",\n format: \"duration\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _never(Class, params) {\n return new Class({\n type: \"never\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _maxLength(maximum, params) {\n return new $ZodCheckMaxLength({\n check: \"max_length\",\n ...normalizeParams(params),\n maximum\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _minLength(minimum, params) {\n return new $ZodCheckMinLength({\n check: \"min_length\",\n ...normalizeParams(params),\n minimum\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _length(length, params) {\n return new $ZodCheckLengthEquals({\n check: \"length_equals\",\n ...normalizeParams(params),\n length\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _regex(pattern, params) {\n return new $ZodCheckRegex({\n check: \"string_format\",\n format: \"regex\",\n ...normalizeParams(params),\n pattern\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _lowercase(params) {\n return new $ZodCheckLowerCase({\n check: \"string_format\",\n format: \"lowercase\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _uppercase(params) {\n return new $ZodCheckUpperCase({\n check: \"string_format\",\n format: \"uppercase\",\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _includes(includes, params) {\n return new $ZodCheckIncludes({\n check: \"string_format\",\n format: \"includes\",\n ...normalizeParams(params),\n includes\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _startsWith(prefix, params) {\n return new $ZodCheckStartsWith({\n check: \"string_format\",\n format: \"starts_with\",\n ...normalizeParams(params),\n prefix\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _endsWith(suffix, params) {\n return new $ZodCheckEndsWith({\n check: \"string_format\",\n format: \"ends_with\",\n ...normalizeParams(params),\n suffix\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _overwrite(tx) {\n return new $ZodCheckOverwrite({\n check: \"overwrite\",\n tx\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _normalize(form) {\n return /* @__PURE__ */ _overwrite((input) => input.normalize(form));\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _trim() {\n return /* @__PURE__ */ _overwrite((input) => input.trim());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _toLowerCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toLowerCase());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _toUpperCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toUpperCase());\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _slugify() {\n return /* @__PURE__ */ _overwrite((input) => slugify(input));\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _array(Class, element, params) {\n return new Class({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _refine(Class, fn, _params) {\n return new Class({\n type: \"custom\",\n check: \"custom\",\n fn,\n ...normalizeParams(_params)\n });\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _superRefine(fn, params) {\n const ch = /* @__PURE__ */ _check((payload) => {\n payload.addIssue = (issue$2) => {\n if (typeof issue$2 === \"string\") payload.issues.push(issue(issue$2, payload.value, ch._zod.def));\n else {\n const _issue = issue$2;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n _issue.input ?? (_issue.input = payload.value);\n _issue.inst ?? (_issue.inst = ch);\n _issue.continue ?? (_issue.continue = !ch._zod.def.abort);\n payload.issues.push(issue(_issue));\n }\n };\n return fn(payload.value, payload);\n }, params);\n return ch;\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction _check(fn, params) {\n const ch = new $ZodCheck({\n check: \"custom\",\n ...normalizeParams(params)\n });\n ch._zod.check = fn;\n return ch;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/to-json-schema.js\nfunction initializeContext(params) {\n let target = params?.target ?? \"draft-2020-12\";\n if (target === \"draft-4\") target = \"draft-04\";\n if (target === \"draft-7\") target = \"draft-07\";\n return {\n processors: params.processors ?? {},\n metadataRegistry: params?.metadata ?? globalRegistry,\n target,\n unrepresentable: params?.unrepresentable ?? \"throw\",\n override: params?.override ?? (() => {}),\n io: params?.io ?? \"output\",\n counter: 0,\n seen: /* @__PURE__ */ new Map(),\n cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n external: params?.external ?? void 0\n };\n}\nfunction process$1(schema, ctx, _params = {\n path: [],\n schemaPath: []\n}) {\n var _a;\n const def = schema._zod.def;\n const seen = ctx.seen.get(schema);\n if (seen) {\n seen.count++;\n if (_params.schemaPath.includes(schema)) seen.cycle = _params.path;\n return seen.schema;\n }\n const result = {\n schema: {},\n count: 1,\n cycle: void 0,\n path: _params.path\n };\n ctx.seen.set(schema, result);\n const overrideSchema = schema._zod.toJSONSchema?.();\n if (overrideSchema) result.schema = overrideSchema;\n else {\n const params = {\n ..._params,\n schemaPath: [..._params.schemaPath, schema],\n path: _params.path\n };\n if (schema._zod.processJSONSchema) schema._zod.processJSONSchema(ctx, result.schema, params);\n else {\n const _json = result.schema;\n const processor = ctx.processors[def.type];\n if (!processor) throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);\n processor(schema, ctx, _json, params);\n }\n const parent = schema._zod.parent;\n if (parent) {\n if (!result.ref) result.ref = parent;\n process$1(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) Object.assign(result.schema, meta);\n if (ctx.io === \"input\" && isTransforming(schema)) {\n delete result.schema.examples;\n delete result.schema.default;\n }\n if (ctx.io === \"input\" && \"_prefault\" in result.schema) (_a = result.schema).default ?? (_a.default = result.schema._prefault);\n delete result.schema._prefault;\n return ctx.seen.get(schema).schema;\n}\nfunction extractDefs(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const idToSchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.entries()) {\n const id = ctx.metadataRegistry.get(entry[0])?.id;\n if (id) {\n const existing = idToSchema.get(id);\n if (existing && existing !== entry[0]) throw new Error(`Duplicate schema id \"${id}\" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);\n idToSchema.set(id, entry[0]);\n }\n }\n const makeURI = (entry) => {\n const defsSegment = ctx.target === \"draft-2020-12\" ? \"$defs\" : \"definitions\";\n if (ctx.external) {\n const externalId = ctx.external.registry.get(entry[0])?.id;\n const uriGenerator = ctx.external.uri ?? ((id) => id);\n if (externalId) return { ref: uriGenerator(externalId) };\n const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;\n entry[1].defId = id;\n return {\n defId: id,\n ref: `${uriGenerator(\"__shared\")}#/${defsSegment}/${id}`\n };\n }\n if (entry[1] === root) return { ref: \"#\" };\n const defUriPrefix = `#/${defsSegment}/`;\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + defId\n };\n };\n const extractToDef = (entry) => {\n if (entry[1].schema.$ref) return;\n const seen = entry[1];\n const { ref, defId } = makeURI(entry);\n seen.def = { ...seen.schema };\n if (defId) seen.defId = defId;\n const schema = seen.schema;\n for (const key in schema) delete schema[key];\n schema.$ref = ref;\n };\n if (ctx.cycles === \"throw\") for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.cycle) throw new Error(`Cycle detected: #/${seen.cycle?.join(\"/\")}/<root>\n\nSet the \\`cycles\\` parameter to \\`\"ref\"\\` to resolve cyclical schemas with defs.`);\n }\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (schema === entry[0]) {\n extractToDef(entry);\n continue;\n }\n if (ctx.external) {\n const ext = ctx.external.registry.get(entry[0])?.id;\n if (schema !== entry[0] && ext) {\n extractToDef(entry);\n continue;\n }\n }\n if (ctx.metadataRegistry.get(entry[0])?.id) {\n extractToDef(entry);\n continue;\n }\n if (seen.cycle) {\n extractToDef(entry);\n continue;\n }\n if (seen.count > 1) {\n if (ctx.reused === \"ref\") {\n extractToDef(entry);\n continue;\n }\n }\n }\n}\nfunction finalize(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const flattenRef = (zodSchema) => {\n const seen = ctx.seen.get(zodSchema);\n if (seen.ref === null) return;\n const schema = seen.def ?? seen.schema;\n const _cached = { ...schema };\n const ref = seen.ref;\n seen.ref = null;\n if (ref) {\n flattenRef(ref);\n const refSeen = ctx.seen.get(ref);\n const refSchema = refSeen.schema;\n if (refSchema.$ref && (ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\")) {\n schema.allOf = schema.allOf ?? [];\n schema.allOf.push(refSchema);\n } else Object.assign(schema, refSchema);\n Object.assign(schema, _cached);\n if (zodSchema._zod.parent === ref) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (!(key in _cached)) delete schema[key];\n }\n if (refSchema.$ref && refSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) delete schema[key];\n }\n }\n const parent = zodSchema._zod.parent;\n if (parent && parent !== ref) {\n flattenRef(parent);\n const parentSeen = ctx.seen.get(parent);\n if (parentSeen?.schema.$ref) {\n schema.$ref = parentSeen.schema.$ref;\n if (parentSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) delete schema[key];\n }\n }\n }\n ctx.override({\n zodSchema,\n jsonSchema: schema,\n path: seen.path ?? []\n });\n };\n for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\n const result = {};\n if (ctx.target === \"draft-2020-12\") result.$schema = \"https://json-schema.org/draft/2020-12/schema\";\n else if (ctx.target === \"draft-07\") result.$schema = \"http://json-schema.org/draft-07/schema#\";\n else if (ctx.target === \"draft-04\") result.$schema = \"http://json-schema.org/draft-04/schema#\";\n else if (ctx.target === \"openapi-3.0\") {}\n if (ctx.external?.uri) {\n const id = ctx.external.registry.get(schema)?.id;\n if (!id) throw new Error(\"Schema is missing an `id` property\");\n result.$id = ctx.external.uri(id);\n }\n Object.assign(result, root.def ?? root.schema);\n const rootMetaId = ctx.metadataRegistry.get(schema)?.id;\n if (rootMetaId !== void 0 && result.id === rootMetaId) delete result.id;\n const defs = ctx.external?.defs ?? {};\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.def && seen.defId) {\n if (seen.def.id === seen.defId) delete seen.def.id;\n defs[seen.defId] = seen.def;\n }\n }\n if (ctx.external) {} else if (Object.keys(defs).length > 0) if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\n try {\n const finalized = JSON.parse(JSON.stringify(result));\n Object.defineProperty(finalized, \"~standard\", {\n value: {\n ...schema[\"~standard\"],\n jsonSchema: {\n input: createStandardJSONSchemaMethod(schema, \"input\", ctx.processors),\n output: createStandardJSONSchemaMethod(schema, \"output\", ctx.processors)\n }\n },\n enumerable: false,\n writable: false\n });\n return finalized;\n } catch (_err) {\n throw new Error(\"Error converting schema to JSON.\");\n }\n}\nfunction isTransforming(_schema, _ctx) {\n const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() };\n if (ctx.seen.has(_schema)) return false;\n ctx.seen.add(_schema);\n const def = _schema._zod.def;\n if (def.type === \"transform\") return true;\n if (def.type === \"array\") return isTransforming(def.element, ctx);\n if (def.type === \"set\") return isTransforming(def.valueType, ctx);\n if (def.type === \"lazy\") return isTransforming(def.getter(), ctx);\n if (def.type === \"promise\" || def.type === \"optional\" || def.type === \"nonoptional\" || def.type === \"nullable\" || def.type === \"readonly\" || def.type === \"default\" || def.type === \"prefault\") return isTransforming(def.innerType, ctx);\n if (def.type === \"intersection\") return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);\n if (def.type === \"record\" || def.type === \"map\") return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);\n if (def.type === \"pipe\") return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);\n if (def.type === \"object\") {\n for (const key in def.shape) if (isTransforming(def.shape[key], ctx)) return true;\n return false;\n }\n if (def.type === \"union\") {\n for (const option of def.options) if (isTransforming(option, ctx)) return true;\n return false;\n }\n if (def.type === \"tuple\") {\n for (const item of def.items) if (isTransforming(item, ctx)) return true;\n if (def.rest && isTransforming(def.rest, ctx)) return true;\n return false;\n }\n return false;\n}\n/**\n* Creates a toJSONSchema method for a schema instance.\n* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing.\n*/\nconst createToJSONSchemaMethod = (schema, processors = {}) => (params) => {\n const ctx = initializeContext({\n ...params,\n processors\n });\n process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\nconst createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {\n const { libraryOptions, target } = params ?? {};\n const ctx = initializeContext({\n ...libraryOptions ?? {},\n target,\n io,\n processors\n });\n process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/core/json-schema-processors.js\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minLength = minimum;\n if (typeof maximum === \"number\") json.maxLength = maximum;\n if (format) {\n json.format = formatMap[format] ?? format;\n if (json.format === \"\") delete json.format;\n if (format === \"time\") delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const regexes = [...patterns];\n if (regexes.length === 1) json.pattern = regexes[0].source;\n else if (regexes.length > 1) json.allOf = [...regexes.map((regex) => ({\n ...ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\" ? { type: \"string\" } : {},\n pattern: regex.source\n }))];\n }\n};\nconst neverProcessor = (_schema, _ctx, json, _params) => {\n json.not = {};\n};\nconst unknownProcessor = (_schema, _ctx, _json, _params) => {};\nconst enumProcessor = (schema, _ctx, json, _params) => {\n const def = schema._zod.def;\n const values = getEnumValues(def.entries);\n if (values.every((v) => typeof v === \"number\")) json.type = \"number\";\n if (values.every((v) => typeof v === \"string\")) json.type = \"string\";\n json.enum = values;\n};\nconst customProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"Transforms cannot be represented in JSON Schema\");\n};\nconst arrayProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const { minimum, maximum } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = process$1(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n json.properties = {};\n const shape = def.shape;\n for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n });\n const allKeys = new Set(Object.keys(shape));\n const requiredKeys = new Set([...allKeys].filter((key) => {\n const v = def.shape[key]._zod;\n if (ctx.io === \"input\") return v.optin === void 0;\n else return v.optout === void 0;\n }));\n if (requiredKeys.size > 0) json.required = Array.from(requiredKeys);\n if (def.catchall?._zod.def.type === \"never\") json.additionalProperties = false;\n else if (!def.catchall) {\n if (ctx.io === \"output\") json.additionalProperties = false;\n } else if (def.catchall) json.additionalProperties = process$1(def.catchall, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n};\nconst unionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const isExclusive = def.inclusive === false;\n const options = def.options.map((x, i) => process$1(x, ctx, {\n ...params,\n path: [\n ...params.path,\n isExclusive ? \"oneOf\" : \"anyOf\",\n i\n ]\n }));\n if (isExclusive) json.oneOf = options;\n else json.anyOf = options;\n};\nconst intersectionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const a = process$1(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = process$1(def.right, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 1\n ]\n });\n const isSimpleIntersection = (val) => \"allOf\" in val && Object.keys(val).length === 1;\n json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\n};\nconst recordProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n const keyType = def.keyType;\n const patterns = keyType._zod.bag?.patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = process$1(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") json.propertyNames = process$1(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n json.additionalProperties = process$1(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n if (keyValues) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues;\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n if (ctx.target === \"openapi-3.0\") {\n seen.ref = def.innerType;\n json.nullable = true;\n } else json.anyOf = [inner, { type: \"null\" }];\n};\nconst nonoptionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.default = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io === \"input\") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n let catchValue;\n try {\n catchValue = def.catchValue(void 0);\n } catch {\n throw new Error(\"Dynamic catch values are not supported in JSON Schema\");\n }\n json.default = catchValue;\n};\nconst pipeProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n const innerType = ctx.io === \"input\" ? def.in._zod.def.type === \"transform\" ? def.out : def.in : def.out;\n process$1(innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = innerType;\n};\nconst readonlyProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.readOnly = true;\n};\nconst optionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/classic/iso.js\nconst ZodISODateTime = /* @__PURE__ */ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction datetime(params) {\n return _isoDateTime(ZodISODateTime, params);\n}\nconst ZodISODate = /* @__PURE__ */ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction date(params) {\n return _isoDate(ZodISODate, params);\n}\nconst ZodISOTime = /* @__PURE__ */ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction time(params) {\n return _isoTime(ZodISOTime, params);\n}\nconst ZodISODuration = /* @__PURE__ */ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction duration(params) {\n return _isoDuration(ZodISODuration, params);\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/classic/errors.js\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n Object.defineProperties(inst, {\n format: { value: (mapper) => formatError(inst, mapper) },\n flatten: { value: (mapper) => flattenError(inst, mapper) },\n addIssue: { value: (issue) => {\n inst.issues.push(issue);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n addIssues: { value: (issues) => {\n inst.issues.push(...issues);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n isEmpty: { get() {\n return inst.issues.length === 0;\n } }\n });\n};\nconst ZodRealError = /* @__PURE__ */ $constructor(\"ZodError\", initializer, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/classic/parse.js\nconst parse = /* @__PURE__ */ _parse(ZodRealError);\nconst parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);\nconst safeParse = /* @__PURE__ */ _safeParse(ZodRealError);\nconst safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);\nconst encode = /* @__PURE__ */ _encode(ZodRealError);\nconst decode = /* @__PURE__ */ _decode(ZodRealError);\nconst encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);\nconst decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);\nconst safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);\nconst safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);\nconst safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);\nconst safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.1/node_modules/zod/v4/classic/schemas.js\nconst _installedGroups = /* @__PURE__ */ new WeakMap();\nfunction _installLazyMethods(inst, group, methods) {\n const proto = Object.getPrototypeOf(inst);\n let installed = _installedGroups.get(proto);\n if (!installed) {\n installed = /* @__PURE__ */ new Set();\n _installedGroups.set(proto, installed);\n }\n if (installed.has(group)) return;\n installed.add(group);\n for (const key in methods) {\n const fn = methods[key];\n Object.defineProperty(proto, key, {\n configurable: true,\n enumerable: false,\n get() {\n const bound = fn.bind(this);\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: bound\n });\n return bound;\n },\n set(v) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: v\n });\n }\n });\n }\n}\nconst ZodType = /* @__PURE__ */ $constructor(\"ZodType\", (inst, def) => {\n $ZodType.init(inst, def);\n Object.assign(inst[\"~standard\"], { jsonSchema: {\n input: createStandardJSONSchemaMethod(inst, \"input\"),\n output: createStandardJSONSchemaMethod(inst, \"output\")\n } });\n inst.toJSONSchema = createToJSONSchemaMethod(inst, {});\n inst.def = def;\n inst.type = def.type;\n Object.defineProperty(inst, \"_def\", { value: def });\n inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });\n inst.safeParse = (data, params) => safeParse(inst, data, params);\n inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });\n inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);\n inst.spa = inst.safeParseAsync;\n inst.encode = (data, params) => encode(inst, data, params);\n inst.decode = (data, params) => decode(inst, data, params);\n inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);\n inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);\n inst.safeEncode = (data, params) => safeEncode(inst, data, params);\n inst.safeDecode = (data, params) => safeDecode(inst, data, params);\n inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);\n inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);\n _installLazyMethods(inst, \"ZodType\", {\n check(...chks) {\n const def = this.def;\n return this.clone(mergeDefs(def, { checks: [...def.checks ?? [], ...chks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)] }), { parent: true });\n },\n with(...chks) {\n return this.check(...chks);\n },\n clone(def, params) {\n return clone(this, def, params);\n },\n brand() {\n return this;\n },\n register(reg, meta) {\n reg.add(this, meta);\n return this;\n },\n refine(check, params) {\n return this.check(refine(check, params));\n },\n superRefine(refinement, params) {\n return this.check(superRefine(refinement, params));\n },\n overwrite(fn) {\n return this.check(_overwrite(fn));\n },\n optional() {\n return optional(this);\n },\n exactOptional() {\n return exactOptional(this);\n },\n nullable() {\n return nullable(this);\n },\n nullish() {\n return optional(nullable(this));\n },\n nonoptional(params) {\n return nonoptional(this, params);\n },\n array() {\n return array(this);\n },\n or(arg) {\n return union([this, arg]);\n },\n and(arg) {\n return intersection(this, arg);\n },\n transform(tx) {\n return pipe(this, transform(tx));\n },\n default(d) {\n return _default(this, d);\n },\n prefault(d) {\n return prefault(this, d);\n },\n catch(params) {\n return _catch(this, params);\n },\n pipe(target) {\n return pipe(this, target);\n },\n readonly() {\n return readonly(this);\n },\n describe(description) {\n const cl = this.clone();\n globalRegistry.add(cl, { description });\n return cl;\n },\n meta(...args) {\n if (args.length === 0) return globalRegistry.get(this);\n const cl = this.clone();\n globalRegistry.add(cl, args[0]);\n return cl;\n },\n isOptional() {\n return this.safeParse(void 0).success;\n },\n isNullable() {\n return this.safeParse(null).success;\n },\n apply(fn) {\n return fn(this);\n }\n });\n Object.defineProperty(inst, \"description\", {\n get() {\n return globalRegistry.get(inst)?.description;\n },\n configurable: true\n });\n return inst;\n});\n/** @internal */\nconst _ZodString = /* @__PURE__ */ $constructor(\"_ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => stringProcessor(inst, ctx, json, params);\n const bag = inst._zod.bag;\n inst.format = bag.format ?? null;\n inst.minLength = bag.minimum ?? null;\n inst.maxLength = bag.maximum ?? null;\n _installLazyMethods(inst, \"_ZodString\", {\n regex(...args) {\n return this.check(_regex(...args));\n },\n includes(...args) {\n return this.check(_includes(...args));\n },\n startsWith(...args) {\n return this.check(_startsWith(...args));\n },\n endsWith(...args) {\n return this.check(_endsWith(...args));\n },\n min(...args) {\n return this.check(_minLength(...args));\n },\n max(...args) {\n return this.check(_maxLength(...args));\n },\n length(...args) {\n return this.check(_length(...args));\n },\n nonempty(...args) {\n return this.check(_minLength(1, ...args));\n },\n lowercase(params) {\n return this.check(_lowercase(params));\n },\n uppercase(params) {\n return this.check(_uppercase(params));\n },\n trim() {\n return this.check(_trim());\n },\n normalize(...args) {\n return this.check(_normalize(...args));\n },\n toLowerCase() {\n return this.check(_toLowerCase());\n },\n toUpperCase() {\n return this.check(_toUpperCase());\n },\n slugify() {\n return this.check(_slugify());\n }\n });\n});\nconst ZodString = /* @__PURE__ */ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n inst.email = (params) => inst.check(_email(ZodEmail, params));\n inst.url = (params) => inst.check(_url(ZodURL, params));\n inst.jwt = (params) => inst.check(_jwt(ZodJWT, params));\n inst.emoji = (params) => inst.check(_emoji(ZodEmoji, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.uuid = (params) => inst.check(_uuid(ZodUUID, params));\n inst.uuidv4 = (params) => inst.check(_uuidv4(ZodUUID, params));\n inst.uuidv6 = (params) => inst.check(_uuidv6(ZodUUID, params));\n inst.uuidv7 = (params) => inst.check(_uuidv7(ZodUUID, params));\n inst.nanoid = (params) => inst.check(_nanoid(ZodNanoID, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.cuid = (params) => inst.check(_cuid(ZodCUID, params));\n inst.cuid2 = (params) => inst.check(_cuid2(ZodCUID2, params));\n inst.ulid = (params) => inst.check(_ulid(ZodULID, params));\n inst.base64 = (params) => inst.check(_base64(ZodBase64, params));\n inst.base64url = (params) => inst.check(_base64url(ZodBase64URL, params));\n inst.xid = (params) => inst.check(_xid(ZodXID, params));\n inst.ksuid = (params) => inst.check(_ksuid(ZodKSUID, params));\n inst.ipv4 = (params) => inst.check(_ipv4(ZodIPv4, params));\n inst.ipv6 = (params) => inst.check(_ipv6(ZodIPv6, params));\n inst.cidrv4 = (params) => inst.check(_cidrv4(ZodCIDRv4, params));\n inst.cidrv6 = (params) => inst.check(_cidrv6(ZodCIDRv6, params));\n inst.e164 = (params) => inst.check(_e164(ZodE164, params));\n inst.datetime = (params) => inst.check(datetime(params));\n inst.date = (params) => inst.check(date(params));\n inst.time = (params) => inst.check(time(params));\n inst.duration = (params) => inst.check(duration(params));\n});\nfunction string(params) {\n return _string(ZodString, params);\n}\nconst ZodStringFormat = /* @__PURE__ */ $constructor(\"ZodStringFormat\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n _ZodString.init(inst, def);\n});\nconst ZodEmail = /* @__PURE__ */ $constructor(\"ZodEmail\", (inst, def) => {\n $ZodEmail.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodGUID = /* @__PURE__ */ $constructor(\"ZodGUID\", (inst, def) => {\n $ZodGUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUUID = /* @__PURE__ */ $constructor(\"ZodUUID\", (inst, def) => {\n $ZodUUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodURL = /* @__PURE__ */ $constructor(\"ZodURL\", (inst, def) => {\n $ZodURL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmoji = /* @__PURE__ */ $constructor(\"ZodEmoji\", (inst, def) => {\n $ZodEmoji.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodNanoID = /* @__PURE__ */ $constructor(\"ZodNanoID\", (inst, def) => {\n $ZodNanoID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst ZodCUID = /* @__PURE__ */ $constructor(\"ZodCUID\", (inst, def) => {\n $ZodCUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCUID2 = /* @__PURE__ */ $constructor(\"ZodCUID2\", (inst, def) => {\n $ZodCUID2.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodULID = /* @__PURE__ */ $constructor(\"ZodULID\", (inst, def) => {\n $ZodULID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodXID = /* @__PURE__ */ $constructor(\"ZodXID\", (inst, def) => {\n $ZodXID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodKSUID = /* @__PURE__ */ $constructor(\"ZodKSUID\", (inst, def) => {\n $ZodKSUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv4 = /* @__PURE__ */ $constructor(\"ZodIPv4\", (inst, def) => {\n $ZodIPv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv6 = /* @__PURE__ */ $constructor(\"ZodIPv6\", (inst, def) => {\n $ZodIPv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv4 = /* @__PURE__ */ $constructor(\"ZodCIDRv4\", (inst, def) => {\n $ZodCIDRv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv6 = /* @__PURE__ */ $constructor(\"ZodCIDRv6\", (inst, def) => {\n $ZodCIDRv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64 = /* @__PURE__ */ $constructor(\"ZodBase64\", (inst, def) => {\n $ZodBase64.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64URL = /* @__PURE__ */ $constructor(\"ZodBase64URL\", (inst, def) => {\n $ZodBase64URL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodE164 = /* @__PURE__ */ $constructor(\"ZodE164\", (inst, def) => {\n $ZodE164.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodJWT = /* @__PURE__ */ $constructor(\"ZodJWT\", (inst, def) => {\n $ZodJWT.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUnknown = /* @__PURE__ */ $constructor(\"ZodUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unknownProcessor(inst, ctx, json, params);\n});\nfunction unknown() {\n return _unknown(ZodUnknown);\n}\nconst ZodNever = /* @__PURE__ */ $constructor(\"ZodNever\", (inst, def) => {\n $ZodNever.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => neverProcessor(inst, ctx, json, params);\n});\nfunction never(params) {\n return _never(ZodNever, params);\n}\nconst ZodArray = /* @__PURE__ */ $constructor(\"ZodArray\", (inst, def) => {\n $ZodArray.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);\n inst.element = def.element;\n _installLazyMethods(inst, \"ZodArray\", {\n min(n, params) {\n return this.check(_minLength(n, params));\n },\n nonempty(params) {\n return this.check(_minLength(1, params));\n },\n max(n, params) {\n return this.check(_maxLength(n, params));\n },\n length(n, params) {\n return this.check(_length(n, params));\n },\n unwrap() {\n return this.element;\n }\n });\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /* @__PURE__ */ $constructor(\"ZodObject\", (inst, def) => {\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n defineLazy(inst, \"shape\", () => {\n return def.shape;\n });\n _installLazyMethods(inst, \"ZodObject\", {\n keyof() {\n return _enum(Object.keys(this._zod.def.shape));\n },\n catchall(catchall) {\n return this.clone({\n ...this._zod.def,\n catchall\n });\n },\n passthrough() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n loose() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n strict() {\n return this.clone({\n ...this._zod.def,\n catchall: never()\n });\n },\n strip() {\n return this.clone({\n ...this._zod.def,\n catchall: void 0\n });\n },\n extend(incoming) {\n return extend(this, incoming);\n },\n safeExtend(incoming) {\n return safeExtend(this, incoming);\n },\n merge(other) {\n return merge(this, other);\n },\n pick(mask) {\n return pick(this, mask);\n },\n omit(mask) {\n return omit(this, mask);\n },\n partial(...args) {\n return partial(ZodOptional, this, args[0]);\n },\n required(...args) {\n return required(ZodNonOptional, this, args[0]);\n }\n });\n});\nfunction object(shape, params) {\n return new ZodObject({\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n });\n}\nconst ZodUnion = /* @__PURE__ */ $constructor(\"ZodUnion\", (inst, def) => {\n $ZodUnion.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params);\n inst.options = def.options;\n});\nfunction union(options, params) {\n return new ZodUnion({\n type: \"union\",\n options,\n ...normalizeParams(params)\n });\n}\nconst ZodIntersection = /* @__PURE__ */ $constructor(\"ZodIntersection\", (inst, def) => {\n $ZodIntersection.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params);\n});\nfunction intersection(left, right) {\n return new ZodIntersection({\n type: \"intersection\",\n left,\n right\n });\n}\nconst ZodRecord = /* @__PURE__ */ $constructor(\"ZodRecord\", (inst, def) => {\n $ZodRecord.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);\n inst.keyType = def.keyType;\n inst.valueType = def.valueType;\n});\nfunction record(keyType, valueType, params) {\n if (!valueType || !valueType._zod) return new ZodRecord({\n type: \"record\",\n keyType: string(),\n valueType: keyType,\n ...normalizeParams(valueType)\n });\n return new ZodRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodEnum = /* @__PURE__ */ $constructor(\"ZodEnum\", (inst, def) => {\n $ZodEnum.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json, params);\n inst.enum = def.entries;\n inst.options = Object.values(def.entries);\n const keys = new Set(Object.keys(def.entries));\n inst.extract = (values, params) => {\n const newEntries = {};\n for (const value of values) if (keys.has(value)) newEntries[value] = def.entries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n inst.exclude = (values, params) => {\n const newEntries = { ...def.entries };\n for (const value of values) if (keys.has(value)) delete newEntries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n});\nfunction _enum(values, params) {\n return new ZodEnum({\n type: \"enum\",\n entries: Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values,\n ...normalizeParams(params)\n });\n}\nconst ZodTransform = /* @__PURE__ */ $constructor(\"ZodTransform\", (inst, def) => {\n $ZodTransform.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx, json, params);\n inst._zod.parse = (payload, _ctx) => {\n if (_ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n payload.addIssue = (issue$1) => {\n if (typeof issue$1 === \"string\") payload.issues.push(issue(issue$1, payload.value, def));\n else {\n const _issue = issue$1;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n _issue.input ?? (_issue.input = payload.value);\n _issue.inst ?? (_issue.inst = inst);\n payload.issues.push(issue(_issue));\n }\n };\n const output = def.transform(payload.value, payload);\n if (output instanceof Promise) return output.then((output) => {\n payload.value = output;\n return payload;\n });\n payload.value = output;\n return payload;\n };\n});\nfunction transform(fn) {\n return new ZodTransform({\n type: \"transform\",\n transform: fn\n });\n}\nconst ZodOptional = /* @__PURE__ */ $constructor(\"ZodOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction optional(innerType) {\n return new ZodOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodExactOptional = /* @__PURE__ */ $constructor(\"ZodExactOptional\", (inst, def) => {\n $ZodExactOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction exactOptional(innerType) {\n return new ZodExactOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodNullable = /* @__PURE__ */ $constructor(\"ZodNullable\", (inst, def) => {\n $ZodNullable.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nullable(innerType) {\n return new ZodNullable({\n type: \"nullable\",\n innerType\n });\n}\nconst ZodDefault = /* @__PURE__ */ $constructor(\"ZodDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeDefault = inst.unwrap;\n});\nfunction _default(innerType, defaultValue) {\n return new ZodDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodPrefault = /* @__PURE__ */ $constructor(\"ZodPrefault\", (inst, def) => {\n $ZodPrefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction prefault(innerType, defaultValue) {\n return new ZodPrefault({\n type: \"prefault\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodNonOptional = /* @__PURE__ */ $constructor(\"ZodNonOptional\", (inst, def) => {\n $ZodNonOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nonoptional(innerType, params) {\n return new ZodNonOptional({\n type: \"nonoptional\",\n innerType,\n ...normalizeParams(params)\n });\n}\nconst ZodCatch = /* @__PURE__ */ $constructor(\"ZodCatch\", (inst, def) => {\n $ZodCatch.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeCatch = inst.unwrap;\n});\nfunction _catch(innerType, catchValue) {\n return new ZodCatch({\n type: \"catch\",\n innerType,\n catchValue: typeof catchValue === \"function\" ? catchValue : () => catchValue\n });\n}\nconst ZodPipe = /* @__PURE__ */ $constructor(\"ZodPipe\", (inst, def) => {\n $ZodPipe.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);\n inst.in = def.in;\n inst.out = def.out;\n});\nfunction pipe(in_, out) {\n return new ZodPipe({\n type: \"pipe\",\n in: in_,\n out\n });\n}\nconst ZodReadonly = /* @__PURE__ */ $constructor(\"ZodReadonly\", (inst, def) => {\n $ZodReadonly.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction readonly(innerType) {\n return new ZodReadonly({\n type: \"readonly\",\n innerType\n });\n}\nconst ZodCustom = /* @__PURE__ */ $constructor(\"ZodCustom\", (inst, def) => {\n $ZodCustom.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);\n});\nfunction refine(fn, _params = {}) {\n return _refine(ZodCustom, fn, _params);\n}\nfunction superRefine(fn, params) {\n return _superRefine(fn, params);\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\nconst HookInputSchema = object({\n session_id: string(),\n transcript_path: string(),\n cwd: string(),\n permission_mode: string(),\n hook_event_name: string(),\n tool_name: string(),\n tool_input: record(string(), unknown())\n});\n/**\n* Read and parse the full stdin as JSON.\n* Returns a result with the validated HookInput, or the raw data and error on failure.\n*/\nasync function readHookInput() {\n const chunks = [];\n for await (const chunk of process.stdin) chunks.push(typeof chunk === \"string\" ? chunk : String(chunk));\n let parsed;\n try {\n parsed = JSON.parse(chunks.join(\"\"));\n } catch (_error) {\n return {\n ok: false,\n raw: chunks.join(\"\").slice(0, 200),\n error: \"invalid JSON\"\n };\n }\n const result = HookInputSchema.safeParse(parsed);\n if (!result.success) return {\n ok: false,\n raw: parsed,\n error: result.error.message\n };\n return {\n ok: true,\n data: result.data\n };\n}\nconst BashInputSchema = object({\n command: string(),\n description: optional(string())\n});\nconst FilePathInputSchema = object({ file_path: string() });\nconst SearchInputSchema = object({\n pattern: string(),\n path: optional(string())\n});\n/** Parse tool_input as Bash input. Returns null if invalid. */\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as file-path input (Read, Write, Edit). Returns null if invalid. */\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as search input (Glob, Grep). Returns null if invalid. */\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDecision(action, reason) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: action,\n ...action === \"deny\" && reason ? { permissionDecisionReason: reason } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\n/**\n* Resolve the audit log path relative to the hook's own location.\n* Co-located at `.claude/hooks/bst-hook.audit.local.jsonl`.\n*/\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\n/**\n* Rotate the log file if it exceeds MAX_LOG_SIZE.\n* Renames current log to `.1` and starts fresh.\n* Safe against concurrent rotation — ENOENT on rename is expected and ignored.\n*/\nfunction rotateIfNeeded(logPath) {\n try {\n if (fs.statSync(logPath).size > MAX_LOG_SIZE) {\n const rotatedPath = `${logPath}.1`;\n fs.renameSync(logPath, rotatedPath);\n }\n } catch (_error) {}\n}\n/** Whether rotation has been checked this session */\nlet rotationChecked = false;\n/**\n* Append an audit entry to the JSONL log.\n* Never throws — audit failures log to stderr and continue.\n*/\nfunction audit(entry) {\n try {\n const logPath = getLogPath();\n if (!rotationChecked) {\n rotateIfNeeded(logPath);\n rotationChecked = true;\n }\n const record = {\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n ...entry\n };\n fs.appendFileSync(logPath, JSON.stringify(record) + \"\\n\");\n } catch (err) {\n process.stderr.write(`[bst-hook] audit write failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n}\n\n//#endregion\n//#region src/lib/shell-tokenizer.ts\n/**\n* Quote-aware shell tokenizer (shlex port).\n*\n* Given a single command segment (no &&/||/;/|), tokenize it into words:\n* - Single quotes: literal, no escape processing\n* - Double quotes: allow \\\", \\\\, \\$, \\` escapes\n* - Backslash outside quotes: escapes next character\n* - Unquoted whitespace splits tokens\n*/\nfunction tokenize(input) {\n const tokens = [];\n let current = \"\";\n let i = 0;\n let inToken = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"'\") {\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated single quote\"\n };\n i++;\n } else if (ch === \"\\\"\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"\\\"\") {\n if (input[i] === \"\\\\\" && i + 1 < input.length) {\n const next = input[i + 1];\n if (next === \"\\\"\" || next === \"\\\\\" || next === \"$\" || next === \"`\") {\n current += next;\n i += 2;\n continue;\n }\n }\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated double quote\"\n };\n i++;\n } else if (ch === \"\\\\\" && i + 1 < input.length) {\n inToken = true;\n current += input[i + 1];\n i += 2;\n } else if (ch === \" \" || ch === \" \") {\n if (inToken) {\n tokens.push(current);\n current = \"\";\n inToken = false;\n }\n i++;\n } else {\n inToken = true;\n current += ch;\n i++;\n }\n }\n if (inToken) tokens.push(current);\n return {\n ok: true,\n tokens\n };\n}\n/**\n* Characters safe to leave unquoted when re-emitting a token. Anything outside\n* this set could be interpreted by the shell-splitter or tokenizer on a\n* subsequent pass (operators, whitespace, quotes, globs, vars, etc.).\n*/\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\n/** Single-quote-wrap a token unless it only contains safe characters. */\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n/**\n* Inverse of `tokenize`: join tokens into a command string that survives\n* round-tripping through `splitCommand` and `tokenize`.\n*\n* Naive `tokens.join(\" \")` is lossy: any token that was originally quoted\n* loses its quote wrapper and may be mis-parsed when split/tokenized again\n* (e.g. `--testPathPattern=a|b` would split on the embedded `|`).\n*/\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\n/**\n* Split a raw command string into segments on unquoted &&, ||, ;, and | operators.\n* Quote-aware: operators inside single or double quotes are not split points.\n*\n* Returns segments with the operator that follows each one (null for the last segment).\n*/\nfunction splitCommand(input) {\n if (!input || !input.trim()) return [];\n const segments = [];\n let current = \"\";\n let i = 0;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\"\" && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\\\" && !inSingleQuote && i + 1 < input.length) {\n current += ch + input[i + 1];\n i += 2;\n continue;\n }\n if (!inSingleQuote && !inDoubleQuote) {\n if (ch === \"&\" && i + 1 < input.length && input[i + 1] === \"&\") {\n pushSegment(segments, current, \"&&\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"&\" && !(i + 1 < input.length && input[i + 1] === \">\") && !(i > 0 && (input[i - 1] === \">\" || input[i - 1] === \"<\"))) {\n pushSegment(segments, current, \"&\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \"|\" && i + 1 < input.length && input[i + 1] === \"|\") {\n pushSegment(segments, current, \"||\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"|\") {\n pushSegment(segments, current, \"|\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \";\") {\n pushSegment(segments, current, \";\");\n current = \"\";\n i++;\n continue;\n }\n }\n current += ch;\n i++;\n }\n const trimmed = current.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator: null\n });\n return segments;\n}\nfunction pushSegment(segments, text, operator) {\n const trimmed = text.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator\n });\n}\n\n//#endregion\n//#region src/lib/injection-guard.ts\n/** Safe redirect patterns that don't constitute file writes */\nconst SAFE_REDIRECTS = new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n]);\n/**\n* Detect dangerous patterns in tokenized command segments.\n*\n* Checks for:\n* - Command substitution (`$(...)`, backticks)\n* - Process substitution (`<(...)`, `>(...)`)\n* - File redirects (`>`, `>>`, `<` to non-/dev/null targets) — skipped when `skipRedirects` is true\n* - Newlines in raw segment (injection via line splitting)\n*\n* @param skipRedirects When true, skip redirect analysis (used for trusted resolved scripts)\n*/\nfunction checkInjection(tokens, rawSegment, skipRedirects = false) {\n if (/[\\n\\r]/.test(rawSegment)) return {\n safe: false,\n reason: \"unquoted newline in command\"\n };\n for (const token of tokens) {\n if (token === \"`\") return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"$(\")) return {\n safe: false,\n reason: \"dollar command substitution\"\n };\n if (token.includes(\"`\")) return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"<(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n if (token.includes(\">(\") && !SAFE_REDIRECTS.has(token)) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n if (!skipRedirects) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (SAFE_REDIRECTS.has(token)) continue;\n if (token.includes(\"<\") && !token.includes(\"<(\")) return {\n safe: false,\n reason: \"input redirect\"\n };\n if (token.includes(\">\")) {\n if (token.endsWith(\">/dev/null\")) continue;\n if (token.match(/^(&>>|&>|>>|>|1>|2>)$/)) {\n if (tokens[i + 1] === \"/dev/null\") continue;\n }\n return {\n safe: false,\n reason: \"output redirect to file\"\n };\n }\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\n/**\n* File extensions considered safe documentation templates.\n* Files ending with these suffixes are NOT treated as secret .env files.\n*/\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\n/**\n* Pattern matching .env files: `.env`, `.env.local`, `.env.production`, etc.\n* Does NOT match `.env.sample`, `.env.example`, `.env.template`.\n*/\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\n/** Check if a filename is a safe .env documentation template. */\nfunction isEnvSafeSuffix(filename) {\n return ENV_SAFE_SUFFIXES.some((suffix) => filename.endsWith(suffix));\n}\n/**\n* Check if a path targets a .env file (excluding safe templates).\n* Works with both bare filenames and full paths.\n*/\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n/**\n* Check if any non-flag token in a Bash command targets a .env file.\n* Used by Bash deny rules for cat, head, tail, grep, etc.\n*/\nfunction tokensTargetEnvFile(tokens) {\n return tokens.slice(1).some((t) => !t.startsWith(\"-\") && isEnvFile(t));\n}\n\n//#endregion\n//#region src/rules.ts\nconst GIT_READ_OPS = new Set([\n \"status\",\n \"log\",\n \"diff\",\n \"show\",\n \"blame\",\n \"grep\",\n \"shortlog\",\n \"range-diff\",\n \"describe\",\n \"branch\",\n \"remote\",\n \"fetch\",\n \"ls-files\",\n \"ls-tree\",\n \"ls-remote\",\n \"show-ref\",\n \"for-each-ref\",\n \"rev-parse\",\n \"rev-list\",\n \"cat-file\",\n \"diff-tree\",\n \"name-rev\",\n \"merge-base\",\n \"check-ignore\"\n]);\n/** Git subcommands that modify the working tree, index, or history. */\nconst GIT_WRITE_OPS = new Set([\n \"push\",\n \"add\",\n \"commit\",\n \"reset\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"checkout\",\n \"switch\",\n \"stash\",\n \"tag\",\n \"revert\",\n \"clean\",\n \"rm\",\n \"mv\"\n]);\n/** Flags on git that enable arbitrary code execution */\nconst GIT_DANGEROUS_FLAGS = new Set([\n \"-c\",\n \"--exec-path\",\n \"--pager\",\n \"--upload-pack\",\n \"--receive-pack\"\n]);\n/** Git global flags that consume the next token as an argument. */\nconst GIT_FLAGS_WITH_ARG = new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/** Extract the git subcommand (first non-flag token after \"git\"), skipping flag arguments. */\nfunction getGitSubcommand(tokens) {\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n return t;\n }\n}\nfunction isGitReadOp(tokens) {\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\") || t.startsWith(\"--output=\")) return false;\n }\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n return stashAction === \"list\" || stashAction === void 0;\n }\n return GIT_READ_OPS.has(sub);\n}\nfunction isGitWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === \"list\" || stashAction === void 0) return false;\n }\n return GIT_WRITE_OPS.has(sub);\n}\n/** Safe stash actions that don't risk data loss. */\nconst STASH_SAFE_ACTIONS = new Set([\n \"push\",\n \"pop\",\n \"apply\",\n \"show\"\n]);\n/**\n* Git write operations that are safe to auto-approve:\n* - `git add` — staging is trivially reversible\n* - `git checkout -b/-B` — branch creation\n* - `git switch -c/-C` — branch creation\n* - `git switch <branch>` — branch switching (switch only handles branches, unlike checkout)\n* - `git stash` / `git stash push|pop|apply|show` — reversible stash operations\n*/\nfunction isGitSafeWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"add\") return true;\n if (sub === \"checkout\") return tokens.slice(tokens.indexOf(\"checkout\") + 1).some((t) => t === \"-b\" || t === \"-B\");\n if (sub === \"switch\") return true;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === void 0) return true;\n return STASH_SAFE_ACTIONS.has(stashAction);\n }\n return false;\n}\n/** pnpm built-in subcommands (not user-defined scripts) */\nconst PNPM_BUILTINS = new Set([\n \"install\",\n \"i\",\n \"add\",\n \"remove\",\n \"rm\",\n \"uninstall\",\n \"un\",\n \"update\",\n \"up\",\n \"upgrade\",\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"audit\",\n \"licenses\",\n \"init\",\n \"create\",\n \"link\",\n \"unlink\",\n \"pack\",\n \"publish\",\n \"config\",\n \"set\",\n \"get\",\n \"store\",\n \"cache\",\n \"rebuild\",\n \"rb\",\n \"prune\",\n \"dedupe\",\n \"dlx\",\n \"exec\",\n \"run\",\n \"patch\",\n \"patch-commit\",\n \"recursive\",\n \"-r\"\n]);\n/** Read-only pnpm commands that are always safe */\nconst PNPM_SAFE_BUILTINS = new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\"\n]);\nfunction isPnpmSafe(tokens) {\n const subTokens = tokens.slice(1);\n let subcommand;\n for (const t of subTokens) {\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n subcommand = t;\n break;\n }\n if (!subcommand) return false;\n if (PNPM_SAFE_BUILTINS.has(subcommand)) return true;\n if (subcommand === \"install\" || subcommand === \"i\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).every((t) => t.startsWith(\"-\"));\n return false;\n}\nconst NPM_READ_OPS = new Set([\n \"view\",\n \"info\",\n \"show\"\n]);\nfunction isNpmReadOp(tokens) {\n const sub = tokens[1];\n return sub !== void 0 && NPM_READ_OPS.has(sub);\n}\n/** Map npm subcommand to a pnpm redirect message */\nfunction getNpmRedirectReason(tokens) {\n const sub = tokens[1];\n const BASE = \"This project uses pnpm.\";\n if (!sub) return `${BASE} Use \\`pnpm\\` instead of npm.`;\n if (sub === \"install\" || sub === \"i\" || sub === \"ci\") {\n if (tokens.slice(2).some((t) => !t.startsWith(\"-\"))) return `${BASE} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`;\n return `${BASE} Use \\`pnpm install\\` instead of npm.`;\n }\n if (sub === \"add\") return `${BASE} Use \\`pnpm add <pkg>\\` instead of npm.`;\n if (sub === \"uninstall\" || sub === \"remove\" || sub === \"rm\" || sub === \"un\" || sub === \"r\") return `${BASE} Use \\`pnpm remove <pkg>\\` instead of npm.`;\n if (sub === \"run\" || sub === \"run-script\") return `${BASE} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`;\n if (sub === \"test\" || sub === \"t\" || sub === \"tst\") return `${BASE} Use \\`pnpm test\\` instead of npm.`;\n if (sub === \"start\") return `${BASE} Use \\`pnpm start\\` instead of npm.`;\n if (sub === \"stop\") return `${BASE} Use \\`pnpm stop\\` instead of npm.`;\n if (sub === \"restart\") return `${BASE} Use \\`pnpm restart\\` instead of npm.`;\n if (sub === \"exec\") return `${BASE} Use \\`pnpm exec <cmd>\\` instead of npm.`;\n if (sub === \"list\" || sub === \"ls\" || sub === \"ll\" || sub === \"la\") return `${BASE} Use \\`pnpm list\\` instead of npm.`;\n if (sub === \"why\") return `${BASE} Use \\`pnpm why\\` instead of npm.`;\n if (sub === \"outdated\") return `${BASE} Use \\`pnpm outdated\\` instead of npm.`;\n if (sub === \"audit\") return `${BASE} Use \\`pnpm audit\\` instead of npm.`;\n if (sub === \"update\" || sub === \"up\" || sub === \"upgrade\") return `${BASE} Use \\`pnpm update\\` instead of npm.`;\n if (sub === \"init\") return `${BASE} Use \\`pnpm init\\` instead of npm.`;\n if (sub === \"publish\") return `${BASE} Use \\`pnpm publish\\` instead of npm.`;\n if (sub === \"link\") return `${BASE} Use \\`pnpm link\\` instead of npm.`;\n if (sub === \"unlink\") return `${BASE} Use \\`pnpm unlink\\` instead of npm.`;\n if (sub === \"pack\") return `${BASE} Use \\`pnpm pack\\` instead of npm.`;\n if (sub === \"cache\") return `${BASE} Use \\`pnpm store\\` instead of \\`npm cache\\`.`;\n if (sub === \"config\" || sub === \"set\" || sub === \"get\") return `${BASE} Use \\`pnpm config\\` instead of npm.`;\n return `${BASE} Use the equivalent pnpm command instead of npm.`;\n}\nfunction arePathArgsSafe(tokens, ctx) {\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n if (path.basename(t).startsWith(\".\")) return false;\n if (t.startsWith(\"~/\") || t === \"~\") return false;\n const resolvedPath = path.resolve(ctx.cwd, t);\n if (resolvedPath !== ctx.cwd && !resolvedPath.startsWith(ctx.cwd + path.sep)) return false;\n }\n return true;\n}\nconst FIND_ALWAYS_DANGEROUS = new Set([\n \"-ok\",\n \"-okdir\",\n \"-delete\",\n \"-fprint\",\n \"-fprint0\",\n \"-fprintf\",\n \"-fls\"\n]);\nconst FIND_EXEC_SAFE_COMMANDS = new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"grep\",\n \"egrep\",\n \"fgrep\",\n \"ls\",\n \"wc\",\n \"file\",\n \"stat\",\n \"basename\",\n \"dirname\",\n \"realpath\",\n \"readlink\",\n \"sha256sum\",\n \"sha1sum\",\n \"md5sum\",\n \"echo\"\n]);\n/**\n* Extracts the command name from a `-exec` or `-execdir` block.\n* Returns the basename of the first token after `-exec`/`-execdir`,\n* or undefined if the block is malformed.\n*/\nfunction findExecCommand(tokens, startIdx) {\n const cmdToken = tokens[startIdx + 1];\n if (!cmdToken || cmdToken === \"{}\" || cmdToken === \";\" || cmdToken === \"+\") return void 0;\n return path.basename(cmdToken);\n}\nfunction isFindSafe(tokens) {\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n if (t === void 0) continue;\n if (FIND_ALWAYS_DANGEROUS.has(t)) return false;\n if (t === \"-exec\" || t === \"-execdir\") {\n const cmd = findExecCommand(tokens, i);\n if (!cmd || !FIND_EXEC_SAFE_COMMANDS.has(cmd)) return false;\n }\n }\n return true;\n}\nfunction isSortSafe(tokens) {\n return !tokens.includes(\"-o\") && !tokens.some((t) => t.startsWith(\"--output\"));\n}\nfunction isRmRecursive(tokens) {\n const rmIdx = tokens.findIndex((t) => path.basename(t) === \"rm\");\n if (rmIdx === -1) return false;\n const afterRm = tokens.slice(rmIdx + 1);\n const shortFlags = afterRm.filter((t) => t.startsWith(\"-\") && !t.startsWith(\"--\"));\n const longFlags = afterRm.filter((t) => t.startsWith(\"--\"));\n const hasRecursiveShort = shortFlags.some((f) => /[rR]/.test(f));\n const hasRecursiveLong = longFlags.includes(\"--recursive\");\n return hasRecursiveShort || hasRecursiveLong;\n}\nfunction isChmod777(tokens) {\n return tokens.some((t) => t === \"777\" || t === \"0777\");\n}\nfunction isGitForcePush(tokens) {\n const pushIdx = tokens.indexOf(\"push\");\n if (pushIdx === -1) return false;\n const afterPush = tokens.slice(pushIdx + 1);\n return afterPush.some((t) => t === \"--force\" || t === \"-f\" && !afterPush.includes(\"--force-with-lease\"));\n}\nfunction isGitResetHardOnMain(tokens) {\n if (!(tokens.includes(\"reset\") && tokens.includes(\"--hard\"))) return false;\n if (tokens.some((t) => t === \"main\" || t === \"master\")) return true;\n const positionalArgs = tokens.filter((t) => t !== \"git\" && t !== \"reset\" && !t.startsWith(\"-\"));\n return positionalArgs.length === 0 || positionalArgs.includes(\"HEAD\");\n}\n/** bst subcommands that are safe to auto-approve (read-only or local-only). */\nconst BST_SAFE_SUBCOMMANDS = new Set([\n \"checks:run\",\n \"hooks:audit\",\n \"hooks:sync\",\n \"docker:build\",\n \"docker:check\",\n \"repo:sync\",\n \"coverage:check\",\n \"coverage:summary\",\n \"coverage:status\"\n]);\nfunction isBstSafe(tokens) {\n const sub = tokens[1];\n if (!sub) return false;\n return BST_SAFE_SUBCOMMANDS.has(sub);\n}\nconst denyRules = [\n {\n name: \"rm-recursive\",\n match: /^rm$/,\n action: \"deny\",\n when: (tokens) => isRmRecursive(tokens),\n reason: \"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"\n },\n {\n name: \"sudo\",\n match: /^sudo$/,\n action: \"deny\",\n reason: \"Don't use sudo. If you need elevated permissions, ask me to do it manually.\"\n },\n {\n name: \"git-force-push\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitForcePush(tokens),\n reason: \"Use `git push --force-with-lease` instead of `--force`.\"\n },\n {\n name: \"git-reset-hard\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitResetHardOnMain(tokens),\n reason: \"Don't `reset --hard` on main. Create a branch first.\"\n },\n {\n name: \"git-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitWriteOp(tokens) && !isGitSafeWriteOp(tokens),\n reason: \"Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"env-file-read\",\n match: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n }\n];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitSafeWriteOp(tokens)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({ allow: array(string()) });\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error — safe default is to not trust the file.\n*/\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n return true;\n } catch {\n return false;\n }\n}\n/**\n* Load repo-level allow patterns from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns an empty array if the file is missing, invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n if (!existsSync(settingsFile)) return [];\n if (!isFileClean(root, SETTINGS_PATH)) return [];\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return [];\n return result.data.allow.filter((s) => typeof s === \"string\" && s.length > 0);\n } catch {\n return [];\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\n*/\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\n*/\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\n/**\n* Find the pnpm workspace root by walking up from `cwd` until a\n* `pnpm-workspace.yaml` file is found. Returns `null` if none is found\n* before reaching the filesystem root.\n*/\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n/**\n* List all workspace package directories.\n*/\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const parentDir = resolve(rootDir, pattern.slice(0, -2));\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\n*/\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\n*/\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\n/**\n* Read the name field from package.json in the given directory.\n*/\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\n*/\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n/**\n* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) — safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\n*/\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n const injection = checkInjection(tokens, rawSegment, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\n }\n const SAFE_REDIRECT_TOKENS = new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"node eval\"\n };\n if (cmdName === \"mv\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"mv\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\n*/\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\n*/\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` flag (both `--filter <pkg>` and `--filter=<pkg>`\n* forms), resolves each against pnpm-workspace.yaml, then classifies the\n* subcommand as built-in or script. Script commands are resolved and evaluated\n* in each filtered package — all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\n*/\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx) {\n const filterPkgs = [];\n let cursor = filterIdx;\n while (cursor < subTokens.length) {\n const t = subTokens[cursor];\n if (t === \"--filter\") {\n const pkg = subTokens[cursor + 1];\n if (!pkg || pkg.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\")) {\n const pkg = t.slice(9);\n if (!pkg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ]).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c → passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\nconst XARGS_ARG_FLAGS = new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\n/** xargs flags that are boolean (no argument) */\nconst XARGS_BOOL_FLAGS = new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\n*/\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = new Set([\"Write\", \"Edit\"]);\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n const hooksDir = path.join(ctx.cwd, \".claude\", \"hooks\");\n return resolved.startsWith(hooksDir + path.sep) || resolved === hooksDir;\n },\n reason: \"Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\"\n },\n {\n name: \"claude-settings-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n return path.resolve(ctx.cwd, filePath) === path.join(ctx.cwd, \".claude\", \"settings.json\");\n },\n reason: \"Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\"\n },\n {\n name: \"outside-cwd-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n if (resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep)) return false;\n const homeClaudeDir = path.join(os.homedir(), \".claude\");\n if (resolved === homeClaudeDir || resolved.startsWith(homeClaudeDir + path.sep)) return false;\n return true;\n },\n reason: \"Don't write files outside the project directory.\"\n }\n];\nconst fileAllowRules = [{\n name: \"cwd-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n return resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep);\n }\n}, {\n name: \"cwd-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => {\n const resolved = path.resolve(ctx.cwd, filePath);\n return resolved === ctx.cwd || resolved.startsWith(ctx.cwd + path.sep);\n }\n}];\n\n//#endregion\n//#region src/evaluate-tool.ts\n/** Tools whose input contains a `file_path` field. */\nconst FILE_PATH_TOOLS = new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\n/** Tools whose input contains a `path` field (search root, optional). */\nconst SEARCH_TOOLS = new Set([\"Glob\", \"Grep\"]);\n/**\n* Extract the relevant file path from tool input based on tool name.\n* Returns null if the input shape doesn't match expectations.\n*/\nfunction extractPath(toolName, toolInput, cwd) {\n if (FILE_PATH_TOOLS.has(toolName)) return parseFilePathInput(toolInput)?.file_path ?? null;\n if (SEARCH_TOOLS.has(toolName)) return parseSearchInput(toolInput)?.path ?? cwd;\n return null;\n}\n/**\n* Evaluate a non-Bash tool invocation against file-tool rules.\n* Returns allow, deny, or passthrough.\n*/\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"passthrough\",\n rule: null,\n reason: \"unknown tool input shape\"\n };\n for (const rule of fileDenyRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n for (const rule of fileAllowRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"no file rule matched\"\n };\n}\n\n//#endregion\n//#region src/index.ts\nconst inputResult = await readHookInput();\nif (!inputResult.ok) {\n process.stderr.write(`[bst-hook] invalid hook input: ${inputResult.error}\\n`);\n audit({\n sessionId: \"unknown\",\n cwd: \"unknown\",\n command: `<invalid input: ${inputResult.error}>`,\n decision: \"passthrough\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`\n });\n process.exit(0);\n}\nconst input = inputResult.data;\nif (input.hook_event_name !== \"PreToolUse\") process.exit(0);\nif (!new Set([\n \"Bash\",\n \"Read\",\n \"Write\",\n \"Edit\",\n \"Glob\",\n \"Grep\"\n]).has(input.tool_name)) process.exit(0);\nconst repoAllowPatterns = loadRepoSettings(input.cwd);\nlet decision;\nlet auditCommand;\nif (input.tool_name === \"Bash\") {\n const bashInput = parseBashInput(input.tool_input);\n if (!bashInput) {\n audit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: `<invalid Bash input: ${JSON.stringify(input.tool_input).slice(0, 200)}>`,\n decision: \"passthrough\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\"\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoAllowPatterns\n });\n auditCommand = bashInput.command;\n} else {\n const projectDir = findWorkspaceRoot(input.cwd) ?? process.env[\"CLAUDE_PROJECT_DIR\"] ?? input.cwd;\n decision = evaluateToolUse(input.tool_name, input.tool_input, { cwd: projectDir });\n const filePath = String(input.tool_input[\"file_path\"] ?? input.tool_input[\"path\"] ?? \"\");\n auditCommand = `${input.tool_name} ${filePath}`;\n}\naudit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: auditCommand,\n decision: decision.action,\n rule: decision.rule,\n reason: decision.reason\n});\nif (decision.action === \"allow\") writeDecision(\"allow\");\nelse if (decision.action === \"deny\") writeDecision(\"deny\", decision.reason);\n\n//#endregion\nexport { };", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** — blocked with a redirect message\n- **Allowed** — auto-approved, no prompt\n- **Passthrough** — falls through to Claude Code's normal permission prompt\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync)\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, branch, remote, fetch, rev-parse, etc.)\n- **Git safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory)\n\n### File tools\n\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv` (file moves)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
|
|
3966
3880
|
}
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3881
|
+
//#endregion
|
|
3882
|
+
//#region src/generators/claude-settings.ts
|
|
3883
|
+
const HookEntrySchema = z.object({
|
|
3884
|
+
type: z.string(),
|
|
3885
|
+
command: z.string()
|
|
3886
|
+
});
|
|
3887
|
+
const HookMatcherSchema = z.object({
|
|
3888
|
+
matcher: z.string(),
|
|
3889
|
+
hooks: z.array(HookEntrySchema).default([])
|
|
3890
|
+
});
|
|
3891
|
+
const ClaudeSettingsSchema = z.object({
|
|
3892
|
+
permissions: z.object({
|
|
3893
|
+
allow: z.array(z.string()).default([]),
|
|
3894
|
+
deny: z.array(z.string()).default([])
|
|
3895
|
+
}).default({
|
|
3896
|
+
allow: [],
|
|
3897
|
+
deny: []
|
|
3898
|
+
}),
|
|
3899
|
+
hooks: z.record(z.string(), z.array(HookMatcherSchema)).default({}),
|
|
3900
|
+
instructions: z.array(z.string()).default([]),
|
|
3901
|
+
enabledPlugins: z.record(z.string(), z.boolean()).default({}),
|
|
3902
|
+
extraKnownMarketplaces: z.record(z.string(), z.record(z.string(), z.unknown())).default({})
|
|
3903
|
+
});
|
|
3904
|
+
function parseClaudeSettings(raw) {
|
|
3905
|
+
try {
|
|
3906
|
+
const json = JSON.parse(raw);
|
|
3907
|
+
const rawResult = z.record(z.string(), z.unknown()).safeParse(json);
|
|
3908
|
+
const settingsResult = ClaudeSettingsSchema.safeParse(json);
|
|
3909
|
+
if (!rawResult.success || !settingsResult.success) return void 0;
|
|
3910
|
+
return {
|
|
3911
|
+
settings: settingsResult.data,
|
|
3912
|
+
rawJson: rawResult.data
|
|
3913
|
+
};
|
|
3914
|
+
} catch {
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
function buildSettings(ctx) {
|
|
3919
|
+
const allow = [];
|
|
3920
|
+
const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
|
|
3921
|
+
const extraKnownMarketplaces = {};
|
|
3922
|
+
if (ctx.config.structure === "monorepo") {
|
|
3923
|
+
if (getMonorepoPackages(ctx.targetDir).some((pkg) => hasWebUIDeps(pkg.dir))) enabledPlugins["frontend-design@claude-plugins-official"] = true;
|
|
3924
|
+
} else if (ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) enabledPlugins["frontend-design@claude-plugins-official"] = true;
|
|
3977
3925
|
return {
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3926
|
+
permissions: {
|
|
3927
|
+
allow,
|
|
3928
|
+
deny: []
|
|
3981
3929
|
},
|
|
3982
|
-
|
|
3930
|
+
hooks: { PreToolUse: [{
|
|
3931
|
+
matcher: "Bash|Read|Write|Edit|Glob|Grep",
|
|
3932
|
+
hooks: [{
|
|
3933
|
+
type: "command",
|
|
3934
|
+
command: `node "$(git rev-parse --show-toplevel)/.claude/hooks/bst-hook.mjs"`
|
|
3935
|
+
}]
|
|
3936
|
+
}] },
|
|
3937
|
+
instructions: [
|
|
3938
|
+
"Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
|
|
3939
|
+
"No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
|
|
3940
|
+
"Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option. For no-empty-function: add a `{ /* no-op */ }` comment body instead of a disable comment.",
|
|
3941
|
+
"Prefer extensionless imports; if an extension is required, use .ts over .js.",
|
|
3942
|
+
"The bst-hook manages Bash/file permissions. See .claude/hooks/bst-hook-guide.md for auto-approved, denied, and passthrough commands."
|
|
3943
|
+
],
|
|
3944
|
+
enabledPlugins,
|
|
3945
|
+
extraKnownMarketplaces
|
|
3983
3946
|
};
|
|
3984
3947
|
}
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3948
|
+
const BST_HOOK_MARKER = "bst-hook.mjs";
|
|
3949
|
+
/** Remove old per-tool bst-hook entries that are now covered by a consolidated matcher. */
|
|
3950
|
+
function removeObsoleteBstHookEntries(matchers, consolidatedMatcher) {
|
|
3951
|
+
const consolidatedTools = new Set(consolidatedMatcher.split("|"));
|
|
3952
|
+
return matchers.filter((m) => {
|
|
3953
|
+
if (!consolidatedTools.has(m.matcher)) return true;
|
|
3954
|
+
if (!m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER))) return true;
|
|
3955
|
+
return false;
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
/** Remove enabledPlugins/extraKnownMarketplaces/hooks when empty to keep the file clean. */
|
|
3959
|
+
function serializeSettings$1(settings) {
|
|
3960
|
+
const { enabledPlugins, extraKnownMarketplaces, hooks, ...rest } = settings;
|
|
3961
|
+
const out = { ...rest };
|
|
3962
|
+
if (Object.keys(hooks).length > 0) out["hooks"] = hooks;
|
|
3963
|
+
if (Object.keys(enabledPlugins).length > 0) out["enabledPlugins"] = enabledPlugins;
|
|
3964
|
+
if (Object.keys(extraKnownMarketplaces).length > 0) out["extraKnownMarketplaces"] = extraKnownMarketplaces;
|
|
3965
|
+
return JSON.stringify(out, null, 2) + "\n";
|
|
3966
|
+
}
|
|
3967
|
+
function writeOrMergeSettings(ctx, filePath, generated) {
|
|
3968
|
+
const existing = ctx.read(filePath);
|
|
3969
|
+
if (existing) {
|
|
3970
|
+
const parsed = parseClaudeSettings(existing);
|
|
3971
|
+
if (!parsed) return {
|
|
3972
|
+
filePath,
|
|
3996
3973
|
action: "skipped",
|
|
3997
3974
|
description: "Could not parse existing settings"
|
|
3998
3975
|
};
|
|
3999
|
-
const {
|
|
4000
|
-
|
|
4001
|
-
|
|
3976
|
+
const { settings, rawJson } = parsed;
|
|
3977
|
+
const missingAllow = generated.permissions.allow.filter((rule) => !settings.permissions.allow.includes(rule));
|
|
3978
|
+
const missingDeny = generated.permissions.deny.filter((rule) => !settings.permissions.deny.includes(rule));
|
|
3979
|
+
const mergedInstructions = [...settings.instructions];
|
|
3980
|
+
let instructionChanges = 0;
|
|
3981
|
+
for (const inst of generated.instructions) {
|
|
3982
|
+
if (mergedInstructions.includes(inst)) continue;
|
|
3983
|
+
const prefixIdx = mergedInstructions.findIndex((e) => inst.startsWith(e) || e.startsWith(inst));
|
|
3984
|
+
if (prefixIdx !== -1) mergedInstructions[prefixIdx] = inst;
|
|
3985
|
+
else mergedInstructions.push(inst);
|
|
3986
|
+
instructionChanges++;
|
|
3987
|
+
}
|
|
3988
|
+
let hookChanges = 0;
|
|
3989
|
+
const mergedHooks = { ...settings.hooks };
|
|
3990
|
+
for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
|
|
3991
|
+
const existingMatchers = mergedHooks[event] ?? [];
|
|
3992
|
+
for (const gm of generatedMatchers) {
|
|
3993
|
+
const existingIdx = existingMatchers.findIndex((m) => m.matcher === gm.matcher && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)));
|
|
3994
|
+
if (existingIdx !== -1) {
|
|
3995
|
+
const existingEntry = existingMatchers[existingIdx];
|
|
3996
|
+
const genCommand = gm.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
|
|
3997
|
+
const curCommand = existingEntry.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
|
|
3998
|
+
if (genCommand && curCommand && genCommand !== curCommand) {
|
|
3999
|
+
const target = (mergedHooks[event] ?? [])[existingIdx];
|
|
4000
|
+
target.hooks = target.hooks.map((h) => h.command.includes(BST_HOOK_MARKER) ? Object.assign(h, { command: genCommand }) : h);
|
|
4001
|
+
hookChanges++;
|
|
4002
|
+
}
|
|
4003
|
+
continue;
|
|
4004
|
+
}
|
|
4005
|
+
if (gm.matcher.includes("|")) {
|
|
4006
|
+
const before = (mergedHooks[event] ?? []).length;
|
|
4007
|
+
mergedHooks[event] = removeObsoleteBstHookEntries(mergedHooks[event] ?? [], gm.matcher);
|
|
4008
|
+
const removed = before - (mergedHooks[event] ?? []).length;
|
|
4009
|
+
if (removed > 0) hookChanges += removed;
|
|
4010
|
+
}
|
|
4011
|
+
mergedHooks[event] = [...mergedHooks[event] ?? [], gm];
|
|
4012
|
+
hookChanges++;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in settings.enabledPlugins));
|
|
4016
|
+
const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in settings.extraKnownMarketplaces));
|
|
4017
|
+
const changed = missingAllow.length + missingDeny.length + hookChanges + instructionChanges + missingPlugins.length + missingMarketplaces.length;
|
|
4018
|
+
if (changed === 0) return {
|
|
4019
|
+
filePath,
|
|
4002
4020
|
action: "skipped",
|
|
4003
|
-
description: "Already has
|
|
4021
|
+
description: "Already has all rules and instructions"
|
|
4004
4022
|
};
|
|
4005
|
-
|
|
4023
|
+
rawJson["permissions"] = {
|
|
4024
|
+
allow: [...settings.permissions.allow, ...missingAllow],
|
|
4025
|
+
deny: [...settings.permissions.deny, ...missingDeny]
|
|
4026
|
+
};
|
|
4027
|
+
if (Object.keys(mergedHooks).length > 0) rawJson["hooks"] = mergedHooks;
|
|
4028
|
+
rawJson["instructions"] = mergedInstructions;
|
|
4029
|
+
const updatedPlugins = { ...settings.enabledPlugins };
|
|
4030
|
+
for (const [key, value] of missingPlugins) updatedPlugins[key] = value;
|
|
4031
|
+
const updatedMarketplaces = { ...settings.extraKnownMarketplaces };
|
|
4032
|
+
for (const [key, value] of missingMarketplaces) updatedMarketplaces[key] = value;
|
|
4033
|
+
if (Object.keys(updatedPlugins).length > 0) rawJson["enabledPlugins"] = updatedPlugins;
|
|
4034
|
+
else delete rawJson["enabledPlugins"];
|
|
4035
|
+
if (Object.keys(updatedMarketplaces).length > 0) rawJson["extraKnownMarketplaces"] = updatedMarketplaces;
|
|
4036
|
+
else delete rawJson["extraKnownMarketplaces"];
|
|
4037
|
+
ctx.write(filePath, JSON.stringify(rawJson, null, 2) + "\n");
|
|
4006
4038
|
return {
|
|
4007
|
-
filePath
|
|
4039
|
+
filePath,
|
|
4008
4040
|
action: "updated",
|
|
4009
|
-
description:
|
|
4041
|
+
description: `Updated ${String(changed)} rules/instructions`
|
|
4010
4042
|
};
|
|
4011
4043
|
}
|
|
4012
|
-
ctx.write(
|
|
4044
|
+
ctx.write(filePath, serializeSettings$1(generated));
|
|
4013
4045
|
return {
|
|
4014
|
-
filePath
|
|
4046
|
+
filePath,
|
|
4015
4047
|
action: "created",
|
|
4016
|
-
description:
|
|
4048
|
+
description: `Generated ${filePath}`
|
|
4017
4049
|
};
|
|
4018
4050
|
}
|
|
4019
|
-
async function
|
|
4051
|
+
async function generateClaudeSettings(ctx) {
|
|
4020
4052
|
const results = [];
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
action: "skipped",
|
|
4034
|
-
description: "Could not find @bensandee/config schema in node_modules"
|
|
4035
|
-
});
|
|
4036
|
-
return results;
|
|
4037
|
-
}
|
|
4038
|
-
const existingSchema = ctx.read(SCHEMA_LOCAL_PATH);
|
|
4039
|
-
if (existingSchema !== void 0 && contentEqual(SCHEMA_LOCAL_PATH, existingSchema, schemaContent)) results.push({
|
|
4040
|
-
filePath: SCHEMA_LOCAL_PATH,
|
|
4041
|
-
action: "skipped",
|
|
4042
|
-
description: "Schema already up to date"
|
|
4043
|
-
});
|
|
4044
|
-
else {
|
|
4045
|
-
ctx.write(SCHEMA_LOCAL_PATH, schemaContent);
|
|
4046
|
-
results.push({
|
|
4047
|
-
filePath: SCHEMA_LOCAL_PATH,
|
|
4048
|
-
action: existingSchema ? "updated" : "created",
|
|
4049
|
-
description: "Copied Forgejo workflow schema from @bensandee/config"
|
|
4050
|
-
});
|
|
4053
|
+
results.push(writeOrMergeSettings(ctx, ".claude/settings.json", buildSettings(ctx)));
|
|
4054
|
+
if (ctx.config.structure === "monorepo") for (const pkg of getMonorepoPackages(ctx.targetDir)) {
|
|
4055
|
+
const pkgRelDir = path.relative(ctx.targetDir, pkg.dir);
|
|
4056
|
+
const claudeDir = path.join(pkgRelDir, ".claude");
|
|
4057
|
+
if (ctx.exists(claudeDir)) {
|
|
4058
|
+
ctx.remove(claudeDir);
|
|
4059
|
+
results.push({
|
|
4060
|
+
filePath: claudeDir,
|
|
4061
|
+
action: "removed",
|
|
4062
|
+
description: `Removed subpackage ${claudeDir} (plugins consolidated at root)`
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4051
4065
|
}
|
|
4052
|
-
results.push(writeSchemaToSettings(ctx));
|
|
4053
4066
|
return results;
|
|
4054
4067
|
}
|
|
4055
4068
|
//#endregion
|
|
4056
|
-
//#region src/
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
function round2$1(n) {
|
|
4066
|
-
return Math.round(n * 100) / 100;
|
|
4067
|
-
}
|
|
4068
|
-
/**
|
|
4069
|
-
* Compare a current run against a baseline across the four overall totals.
|
|
4070
|
-
* A total passes when current >= baseline OR (baseline - current) <= tolerancePp.
|
|
4071
|
-
*/
|
|
4072
|
-
function compareTotals(baseline, current, tolerancePp = DEFAULT_TOLERANCE_PP) {
|
|
4073
|
-
const totals = TOTAL_KEYS.map((key) => {
|
|
4074
|
-
const b = baseline[key];
|
|
4075
|
-
const c = current[key];
|
|
4076
|
-
return {
|
|
4077
|
-
key,
|
|
4078
|
-
baseline: b,
|
|
4079
|
-
current: c,
|
|
4080
|
-
delta: round2$1(c - b),
|
|
4081
|
-
pass: c >= b || b - c <= tolerancePp
|
|
4082
|
-
};
|
|
4083
|
-
});
|
|
4084
|
-
return {
|
|
4085
|
-
pass: totals.every((t) => t.pass),
|
|
4086
|
-
tolerancePp,
|
|
4087
|
-
totals
|
|
4069
|
+
//#region src/generators/release-it.ts
|
|
4070
|
+
function buildConfig$3(ci, isMonorepo) {
|
|
4071
|
+
const config = {
|
|
4072
|
+
$schema: "https://unpkg.com/release-it/schema/release-it.json",
|
|
4073
|
+
git: {
|
|
4074
|
+
commitMessage: "chore: release v${version}",
|
|
4075
|
+
tagName: "v${version}"
|
|
4076
|
+
},
|
|
4077
|
+
npm: { publish: true }
|
|
4088
4078
|
};
|
|
4089
|
-
}
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
total: z.number(),
|
|
4098
|
-
covered: z.number(),
|
|
4099
|
-
skipped: z.number().optional(),
|
|
4100
|
-
pct: z.number()
|
|
4101
|
-
});
|
|
4102
|
-
const FileEntrySchema = z.object({
|
|
4103
|
-
lines: TotalEntrySchema,
|
|
4104
|
-
statements: TotalEntrySchema,
|
|
4105
|
-
branches: TotalEntrySchema,
|
|
4106
|
-
functions: TotalEntrySchema
|
|
4107
|
-
});
|
|
4108
|
-
/**
|
|
4109
|
-
* Schema for v8 / istanbul `coverage/coverage-summary.json`. The "total" key
|
|
4110
|
-
* holds the grand totals; every other key is a per-file path → entry mapping.
|
|
4111
|
-
*/
|
|
4112
|
-
const V8CoverageSummarySchema = z.object({ total: FileEntrySchema }).catchall(FileEntrySchema);
|
|
4113
|
-
const TotalsSchema = z.object({
|
|
4114
|
-
lines: z.number(),
|
|
4115
|
-
statements: z.number(),
|
|
4116
|
-
branches: z.number(),
|
|
4117
|
-
functions: z.number()
|
|
4118
|
-
});
|
|
4119
|
-
const PackageEntrySchema = z.object({
|
|
4120
|
-
lines: z.number(),
|
|
4121
|
-
branches: z.number()
|
|
4122
|
-
});
|
|
4123
|
-
/** Coverage record without identity fields — body shape for `coverage-baseline.json`. */
|
|
4124
|
-
const CoverageBaselineSchema = z.object({
|
|
4125
|
-
totals: TotalsSchema,
|
|
4126
|
-
packages: z.record(z.string(), PackageEntrySchema)
|
|
4127
|
-
});
|
|
4128
|
-
/** Full trimmed record — appended to `coverage-history.jsonl` and emitted by `coverage:summary`. */
|
|
4129
|
-
const CoverageRecordSchema = CoverageBaselineSchema.extend({
|
|
4130
|
-
sha: z.string(),
|
|
4131
|
-
timestamp: z.string()
|
|
4132
|
-
});
|
|
4133
|
-
/** Parse a v8 `coverage-summary.json` blob; throws FatalError on malformed input. */
|
|
4134
|
-
function parseV8Summary(raw) {
|
|
4135
|
-
let json;
|
|
4136
|
-
try {
|
|
4137
|
-
json = JSON.parse(raw);
|
|
4138
|
-
} catch (error) {
|
|
4139
|
-
throw new FatalError(`coverage-summary.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4140
|
-
}
|
|
4141
|
-
const result = V8CoverageSummarySchema.safeParse(json);
|
|
4142
|
-
if (!result.success) throw new FatalError(`coverage-summary.json shape doesn't match v8 / istanbul output: ${result.error.message}`);
|
|
4143
|
-
return result.data;
|
|
4144
|
-
}
|
|
4145
|
-
/** Parse a `coverage-baseline.json` blob; throws FatalError on malformed input. */
|
|
4146
|
-
function parseBaseline(raw) {
|
|
4147
|
-
let json;
|
|
4148
|
-
try {
|
|
4149
|
-
json = JSON.parse(raw);
|
|
4150
|
-
} catch (error) {
|
|
4151
|
-
throw new FatalError(`coverage-baseline.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4079
|
+
if (ci === "github") config["github"] = { release: true };
|
|
4080
|
+
const plugins = {};
|
|
4081
|
+
if (isMonorepo) {
|
|
4082
|
+
config["npm"] = {
|
|
4083
|
+
publish: true,
|
|
4084
|
+
allowSameVersion: true
|
|
4085
|
+
};
|
|
4086
|
+
plugins["@release-it/bumper"] = { out: "packages/*/package.json" };
|
|
4152
4087
|
}
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
return result.data;
|
|
4088
|
+
if (Object.keys(plugins).length > 0) config["plugins"] = plugins;
|
|
4089
|
+
return config;
|
|
4156
4090
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4091
|
+
async function generateReleaseIt(ctx) {
|
|
4092
|
+
const filePath = ".release-it.json";
|
|
4093
|
+
if (ctx.config.releaseStrategy !== "release-it") return {
|
|
4094
|
+
filePath,
|
|
4095
|
+
action: "skipped",
|
|
4096
|
+
description: "release-it not requested"
|
|
4097
|
+
};
|
|
4098
|
+
const content = JSON.stringify(buildConfig$3(ctx.config.ci, ctx.config.structure === "monorepo"), null, 2) + "\n";
|
|
4099
|
+
const existing = ctx.read(filePath);
|
|
4100
|
+
if (existing) {
|
|
4101
|
+
if (contentEqual(filePath, existing, content)) return {
|
|
4102
|
+
filePath,
|
|
4103
|
+
action: "skipped",
|
|
4104
|
+
description: "Already configured"
|
|
4105
|
+
};
|
|
4106
|
+
if (await ctx.confirmOverwrite(filePath) === "skip") return {
|
|
4107
|
+
filePath,
|
|
4108
|
+
action: "skipped",
|
|
4109
|
+
description: "Existing release-it config preserved"
|
|
4110
|
+
};
|
|
4164
4111
|
}
|
|
4165
|
-
|
|
4166
|
-
return
|
|
4112
|
+
ctx.write(filePath, content);
|
|
4113
|
+
return {
|
|
4114
|
+
filePath,
|
|
4115
|
+
action: existing ? "updated" : "created",
|
|
4116
|
+
description: "Generated .release-it.json"
|
|
4117
|
+
};
|
|
4167
4118
|
}
|
|
4168
4119
|
//#endregion
|
|
4169
|
-
//#region src/
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4120
|
+
//#region src/generators/changesets.ts
|
|
4121
|
+
function buildConfig$2() {
|
|
4122
|
+
return {
|
|
4123
|
+
$schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
4124
|
+
changelog: "@changesets/cli/changelog",
|
|
4125
|
+
commit: false,
|
|
4126
|
+
fixed: [],
|
|
4127
|
+
linked: [],
|
|
4128
|
+
access: "public",
|
|
4129
|
+
baseBranch: "main",
|
|
4130
|
+
updateInternalDependencies: "patch",
|
|
4131
|
+
ignore: []
|
|
4132
|
+
};
|
|
4175
4133
|
}
|
|
4176
|
-
function
|
|
4177
|
-
const
|
|
4178
|
-
if (
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
const
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4134
|
+
async function generateChangesets(ctx) {
|
|
4135
|
+
const filePath = ".changeset/config.json";
|
|
4136
|
+
if (ctx.config.releaseStrategy !== "changesets") return {
|
|
4137
|
+
filePath,
|
|
4138
|
+
action: "skipped",
|
|
4139
|
+
description: "Changesets not requested"
|
|
4140
|
+
};
|
|
4141
|
+
const content = JSON.stringify(buildConfig$2(), null, 2) + "\n";
|
|
4142
|
+
const existing = ctx.read(filePath);
|
|
4143
|
+
if (existing) return {
|
|
4144
|
+
filePath,
|
|
4145
|
+
action: "skipped",
|
|
4146
|
+
description: "Existing changesets config preserved"
|
|
4147
|
+
};
|
|
4148
|
+
ctx.write(filePath, content);
|
|
4190
4149
|
return {
|
|
4191
|
-
|
|
4192
|
-
|
|
4150
|
+
filePath,
|
|
4151
|
+
action: existing ? "updated" : "created",
|
|
4152
|
+
description: "Generated .changeset/config.json"
|
|
4193
4153
|
};
|
|
4194
4154
|
}
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4155
|
+
//#endregion
|
|
4156
|
+
//#region src/generators/lefthook.ts
|
|
4157
|
+
const LINT_COMMAND = "pnpm exec oxlint --no-error-on-unmatched-pattern {staged_files}";
|
|
4158
|
+
function formatCommandFor(formatter) {
|
|
4159
|
+
return formatter === "prettier" ? "pnpm exec prettier --ignore-unknown --no-error-on-unmatched-pattern --write {staged_files}" : "pnpm exec oxfmt --no-error-on-unmatched-pattern {staged_files}";
|
|
4199
4160
|
}
|
|
4200
|
-
function
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
});
|
|
4209
|
-
if (result.exitCode !== 0) throw new FatalError(`git hash-object failed: ${result.stderr.trim() || "unknown error"}`);
|
|
4210
|
-
return result.stdout.trim();
|
|
4161
|
+
function requiredCommands(formatter) {
|
|
4162
|
+
return {
|
|
4163
|
+
lint: { run: LINT_COMMAND },
|
|
4164
|
+
format: {
|
|
4165
|
+
run: formatCommandFor(formatter),
|
|
4166
|
+
stage_fixed: true
|
|
4167
|
+
}
|
|
4168
|
+
};
|
|
4211
4169
|
}
|
|
4212
|
-
function
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4170
|
+
function buildConfig$1(formatter) {
|
|
4171
|
+
return [
|
|
4172
|
+
"pre-commit:",
|
|
4173
|
+
" commands:",
|
|
4174
|
+
" lint:",
|
|
4175
|
+
` run: ${LINT_COMMAND}`,
|
|
4176
|
+
" format:",
|
|
4177
|
+
` run: ${formatCommandFor(formatter)}`,
|
|
4178
|
+
" stage_fixed: true",
|
|
4179
|
+
""
|
|
4180
|
+
].join("\n");
|
|
4220
4181
|
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4182
|
+
const ARCHIVE_DIR = ".tooling-archived";
|
|
4183
|
+
/** Common client-side git hooks that husky may have configured. */
|
|
4184
|
+
const HUSKY_HOOK_NAMES = [
|
|
4185
|
+
"pre-commit",
|
|
4186
|
+
"commit-msg",
|
|
4187
|
+
"pre-push",
|
|
4188
|
+
"post-merge",
|
|
4189
|
+
"post-checkout",
|
|
4190
|
+
"prepare-commit-msg"
|
|
4191
|
+
];
|
|
4192
|
+
/** All known lint-staged config file locations to archive. */
|
|
4193
|
+
const LINT_STAGED_CONFIG_PATHS = [
|
|
4194
|
+
"lint-staged.config.mjs",
|
|
4195
|
+
"lint-staged.config.js",
|
|
4196
|
+
"lint-staged.config.cjs",
|
|
4197
|
+
".lintstagedrc",
|
|
4198
|
+
".lintstagedrc.json",
|
|
4199
|
+
".lintstagedrc.yaml",
|
|
4200
|
+
".lintstagedrc.yml",
|
|
4201
|
+
".lintstagedrc.mjs",
|
|
4202
|
+
".lintstagedrc.cjs"
|
|
4203
|
+
];
|
|
4204
|
+
/** All known lefthook config file locations, in priority order. */
|
|
4205
|
+
const LEFTHOOK_CONFIG_PATHS = ["lefthook.yml", ".lefthook.yml"];
|
|
4206
|
+
/** Archive all husky hook files found in .husky/ */
|
|
4207
|
+
function archiveHuskyHooks(ctx, results) {
|
|
4208
|
+
let found = false;
|
|
4209
|
+
for (const hook of HUSKY_HOOK_NAMES) {
|
|
4210
|
+
const huskyPath = `.husky/${hook}`;
|
|
4211
|
+
const existing = ctx.read(huskyPath);
|
|
4212
|
+
if (existing !== void 0) {
|
|
4213
|
+
ctx.write(`${ARCHIVE_DIR}/${huskyPath}`, existing);
|
|
4214
|
+
ctx.remove(huskyPath);
|
|
4215
|
+
results.push({
|
|
4216
|
+
filePath: huskyPath,
|
|
4217
|
+
action: "archived",
|
|
4218
|
+
description: `Moved to ${ARCHIVE_DIR}/${huskyPath}`
|
|
4219
|
+
});
|
|
4220
|
+
found = true;
|
|
4221
|
+
}
|
|
4230
4222
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
if (result.exitCode !== 0) throw new FatalError(`git commit-tree failed: ${result.stderr.trim() || "unknown error"}`);
|
|
4249
|
-
return result.stdout.trim();
|
|
4250
|
-
}
|
|
4251
|
-
function pushCommit(executor, config, commitSha, parentSha) {
|
|
4252
|
-
const remote = config.remote ?? "origin";
|
|
4253
|
-
const refSpec = `${commitSha}:refs/heads/${config.historyBranch}`;
|
|
4254
|
-
if (parentSha === void 0) return executor.exec("git", [
|
|
4255
|
-
"push",
|
|
4256
|
-
remote,
|
|
4257
|
-
refSpec
|
|
4258
|
-
], { cwd: config.cwd });
|
|
4259
|
-
const lease = `--force-with-lease=refs/heads/${config.historyBranch}:${parentSha}`;
|
|
4260
|
-
return executor.exec("git", [
|
|
4261
|
-
"push",
|
|
4262
|
-
lease,
|
|
4263
|
-
remote,
|
|
4264
|
-
refSpec
|
|
4265
|
-
], { cwd: config.cwd });
|
|
4266
|
-
}
|
|
4267
|
-
function shortSha(sha) {
|
|
4268
|
-
return sha.slice(0, 7);
|
|
4269
|
-
}
|
|
4270
|
-
/**
|
|
4271
|
-
* Append a coverage record to the orphan branch. Bootstraps the branch on first run.
|
|
4272
|
-
* Uses pure git plumbing (no worktree / no checkout) so the working tree stays untouched.
|
|
4273
|
-
* On push conflict (concurrent record), refetches and rebuilds the commit (up to MAX_PUSH_ATTEMPTS).
|
|
4274
|
-
*/
|
|
4275
|
-
function appendRecord(executor, config, record) {
|
|
4276
|
-
const line = JSON.stringify(record) + "\n";
|
|
4277
|
-
let lastError = "";
|
|
4278
|
-
for (let attempt = 1; attempt <= MAX_PUSH_ATTEMPTS; attempt++) {
|
|
4279
|
-
const fetched = fetchBranch(executor, config);
|
|
4280
|
-
const newContent = (fetched.exists ? readRemoteHistory(executor, config) : "") + line;
|
|
4281
|
-
const blobSha = makeBlob(executor, config.cwd, newContent);
|
|
4282
|
-
const treeSha = makeTree(executor, config.cwd, blobSha);
|
|
4283
|
-
const message = `chore(coverage): record ${shortSha(record.sha)} [skip ci]`;
|
|
4284
|
-
const commitSha = makeCommit(executor, config, treeSha, fetched.parentSha, message);
|
|
4285
|
-
const pushResult = pushCommit(executor, config, commitSha, fetched.parentSha);
|
|
4286
|
-
if (pushResult.exitCode === 0) return {
|
|
4287
|
-
commitSha,
|
|
4288
|
-
bootstrapped: !fetched.exists,
|
|
4289
|
-
attempts: attempt
|
|
4290
|
-
};
|
|
4291
|
-
lastError = pushResult.stderr.trim() || pushResult.stdout.trim() || "unknown push error";
|
|
4223
|
+
return found;
|
|
4224
|
+
}
|
|
4225
|
+
/** Archive all lint-staged config files. */
|
|
4226
|
+
function archiveLintStagedConfigs(ctx, results) {
|
|
4227
|
+
let found = false;
|
|
4228
|
+
for (const lsPath of LINT_STAGED_CONFIG_PATHS) {
|
|
4229
|
+
const existing = ctx.read(lsPath);
|
|
4230
|
+
if (existing !== void 0) {
|
|
4231
|
+
ctx.write(`${ARCHIVE_DIR}/${lsPath}`, existing);
|
|
4232
|
+
ctx.remove(lsPath);
|
|
4233
|
+
results.push({
|
|
4234
|
+
filePath: lsPath,
|
|
4235
|
+
action: "archived",
|
|
4236
|
+
description: `Moved to ${ARCHIVE_DIR}/${lsPath}`
|
|
4237
|
+
});
|
|
4238
|
+
found = true;
|
|
4239
|
+
}
|
|
4292
4240
|
}
|
|
4293
|
-
|
|
4241
|
+
return found;
|
|
4294
4242
|
}
|
|
4295
|
-
/**
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
if (
|
|
4243
|
+
/** Remove husky/lint-staged from package.json devDependencies and fix prepare script. */
|
|
4244
|
+
function cleanPackageJson(ctx, results) {
|
|
4245
|
+
const raw = ctx.read("package.json");
|
|
4246
|
+
if (!raw) return;
|
|
4247
|
+
const pkg = parsePackageJson(raw);
|
|
4248
|
+
if (!pkg) return;
|
|
4249
|
+
const changes = [];
|
|
4250
|
+
if (pkg.devDependencies) {
|
|
4251
|
+
if ("husky" in pkg.devDependencies) {
|
|
4252
|
+
delete pkg.devDependencies["husky"];
|
|
4253
|
+
changes.push("removed devDependency: husky");
|
|
4254
|
+
}
|
|
4255
|
+
if ("lint-staged" in pkg.devDependencies) {
|
|
4256
|
+
delete pkg.devDependencies["lint-staged"];
|
|
4257
|
+
changes.push("removed devDependency: lint-staged");
|
|
4258
|
+
}
|
|
4308
4259
|
}
|
|
4309
|
-
|
|
4260
|
+
if (pkg.scripts?.["prepare"] && /\bhusky\b/.test(pkg.scripts["prepare"])) {
|
|
4261
|
+
delete pkg.scripts["prepare"];
|
|
4262
|
+
changes.push("removed husky prepare script");
|
|
4263
|
+
}
|
|
4264
|
+
if (changes.length === 0) return;
|
|
4265
|
+
ctx.write("package.json", JSON.stringify(pkg, null, 2) + "\n");
|
|
4266
|
+
results.push({
|
|
4267
|
+
filePath: "package.json",
|
|
4268
|
+
action: "updated",
|
|
4269
|
+
description: changes.join(", ")
|
|
4270
|
+
});
|
|
4271
|
+
}
|
|
4272
|
+
async function generateLefthook(ctx) {
|
|
4273
|
+
const filePath = "lefthook.yml";
|
|
4274
|
+
const content = buildConfig$1(ctx.config.formatter);
|
|
4275
|
+
const results = [];
|
|
4276
|
+
archiveHuskyHooks(ctx, results);
|
|
4277
|
+
archiveLintStagedConfigs(ctx, results);
|
|
4278
|
+
cleanPackageJson(ctx, results);
|
|
4279
|
+
const existingPath = LEFTHOOK_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
4280
|
+
if (existingPath) {
|
|
4281
|
+
const existing = ctx.read(existingPath);
|
|
4282
|
+
if (existing) {
|
|
4283
|
+
const merged = mergeLefthookCommands(existing, requiredCommands(ctx.config.formatter));
|
|
4284
|
+
if (merged.changed) {
|
|
4285
|
+
ctx.write(existingPath, merged.content);
|
|
4286
|
+
results.push({
|
|
4287
|
+
filePath: existingPath,
|
|
4288
|
+
action: "updated",
|
|
4289
|
+
description: "Added missing pre-commit commands"
|
|
4290
|
+
});
|
|
4291
|
+
} else results.push({
|
|
4292
|
+
filePath: existingPath,
|
|
4293
|
+
action: "skipped",
|
|
4294
|
+
description: "Lefthook config already up to date"
|
|
4295
|
+
});
|
|
4296
|
+
} else results.push({
|
|
4297
|
+
filePath: existingPath,
|
|
4298
|
+
action: "skipped",
|
|
4299
|
+
description: "Could not read existing lefthook config"
|
|
4300
|
+
});
|
|
4301
|
+
return results;
|
|
4302
|
+
}
|
|
4303
|
+
ctx.write(filePath, content);
|
|
4304
|
+
results.push({
|
|
4305
|
+
filePath,
|
|
4306
|
+
action: "created",
|
|
4307
|
+
description: "Generated lefthook pre-commit config"
|
|
4308
|
+
});
|
|
4309
|
+
return results;
|
|
4310
4310
|
}
|
|
4311
4311
|
//#endregion
|
|
4312
|
-
//#region src/
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4312
|
+
//#region src/generators/vscode-settings.ts
|
|
4313
|
+
const SCHEMA_NPM_PATH = "@bensandee/config/schemas/forgejo-workflow.schema.json";
|
|
4314
|
+
const SCHEMA_LOCAL_PATH = ".vscode/forgejo-workflow.schema.json";
|
|
4315
|
+
const SETTINGS_PATH = ".vscode/settings.json";
|
|
4316
|
+
const SCHEMA_GLOB = ".forgejo/workflows/*.{yml,yaml}";
|
|
4317
|
+
const VscodeSettingsSchema = z.looseObject({ "yaml.schemas": z.record(z.string(), z.unknown()).default({}) });
|
|
4318
|
+
function readSchemaFromNodeModules(targetDir) {
|
|
4319
|
+
const candidate = path.join(targetDir, "node_modules", SCHEMA_NPM_PATH);
|
|
4320
|
+
if (!existsSync(candidate)) return void 0;
|
|
4321
|
+
return readFileSync(candidate, "utf-8");
|
|
4322
|
+
}
|
|
4323
|
+
function serializeSettings(settings) {
|
|
4324
|
+
return JSON.stringify(settings, null, 2) + "\n";
|
|
4325
|
+
}
|
|
4326
|
+
const YamlSchemasSchema = z.record(z.string(), z.unknown());
|
|
4327
|
+
/** Merge yaml.schemas into a settings object. Returns the result and whether anything changed. */
|
|
4328
|
+
function mergeYamlSchemas(settings) {
|
|
4329
|
+
const parsed = YamlSchemasSchema.safeParse(settings["yaml.schemas"]);
|
|
4330
|
+
const yamlSchemas = parsed.success ? { ...parsed.data } : {};
|
|
4331
|
+
if (SCHEMA_LOCAL_PATH in yamlSchemas) return {
|
|
4332
|
+
merged: settings,
|
|
4333
|
+
changed: false
|
|
4321
4334
|
};
|
|
4322
|
-
|
|
4323
|
-
const overrides = inputs.coverage ?? {};
|
|
4335
|
+
yamlSchemas[SCHEMA_LOCAL_PATH] = SCHEMA_GLOB;
|
|
4324
4336
|
return {
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4337
|
+
merged: {
|
|
4338
|
+
...settings,
|
|
4339
|
+
"yaml.schemas": yamlSchemas
|
|
4340
|
+
},
|
|
4341
|
+
changed: true
|
|
4330
4342
|
};
|
|
4331
4343
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4344
|
+
function writeSchemaToSettings(ctx) {
|
|
4345
|
+
if (ctx.exists(SETTINGS_PATH)) {
|
|
4346
|
+
const raw = ctx.read(SETTINGS_PATH);
|
|
4347
|
+
if (!raw) return {
|
|
4348
|
+
filePath: SETTINGS_PATH,
|
|
4349
|
+
action: "skipped",
|
|
4350
|
+
description: "Could not read existing settings"
|
|
4351
|
+
};
|
|
4352
|
+
const parsed = VscodeSettingsSchema.safeParse(parse(raw));
|
|
4353
|
+
if (!parsed.success) return {
|
|
4354
|
+
filePath: SETTINGS_PATH,
|
|
4355
|
+
action: "skipped",
|
|
4356
|
+
description: "Could not parse existing settings"
|
|
4357
|
+
};
|
|
4358
|
+
const { merged, changed } = mergeYamlSchemas(parsed.data);
|
|
4359
|
+
if (!changed) return {
|
|
4360
|
+
filePath: SETTINGS_PATH,
|
|
4361
|
+
action: "skipped",
|
|
4362
|
+
description: "Already has Forgejo schema mapping"
|
|
4363
|
+
};
|
|
4364
|
+
ctx.write(SETTINGS_PATH, serializeSettings(merged));
|
|
4365
|
+
return {
|
|
4366
|
+
filePath: SETTINGS_PATH,
|
|
4367
|
+
action: "updated",
|
|
4368
|
+
description: "Added Forgejo workflow schema mapping"
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
ctx.write(SETTINGS_PATH, serializeSettings({ "yaml.schemas": { [SCHEMA_LOCAL_PATH]: SCHEMA_GLOB } }));
|
|
4372
|
+
return {
|
|
4373
|
+
filePath: SETTINGS_PATH,
|
|
4374
|
+
action: "created",
|
|
4375
|
+
description: "Generated .vscode/settings.json with Forgejo workflow schema"
|
|
4376
|
+
};
|
|
4343
4377
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
hasVitestConfig: reader.exists("vitest.config.ts") || reader.exists("vitest.config.mts"),
|
|
4369
|
-
setupVitest,
|
|
4370
|
-
coverage
|
|
4378
|
+
async function generateVscodeSettings(ctx) {
|
|
4379
|
+
const results = [];
|
|
4380
|
+
if (ctx.config.ci !== "forgejo") {
|
|
4381
|
+
results.push({
|
|
4382
|
+
filePath: SETTINGS_PATH,
|
|
4383
|
+
action: "skipped",
|
|
4384
|
+
description: "Not a Forgejo project"
|
|
4385
|
+
});
|
|
4386
|
+
return results;
|
|
4387
|
+
}
|
|
4388
|
+
const schemaContent = readSchemaFromNodeModules(ctx.targetDir);
|
|
4389
|
+
if (!schemaContent) {
|
|
4390
|
+
results.push({
|
|
4391
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
4392
|
+
action: "skipped",
|
|
4393
|
+
description: "Could not find @bensandee/config schema in node_modules"
|
|
4394
|
+
});
|
|
4395
|
+
return results;
|
|
4396
|
+
}
|
|
4397
|
+
const existingSchema = ctx.read(SCHEMA_LOCAL_PATH);
|
|
4398
|
+
if (existingSchema !== void 0 && contentEqual(SCHEMA_LOCAL_PATH, existingSchema, schemaContent)) results.push({
|
|
4399
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
4400
|
+
action: "skipped",
|
|
4401
|
+
description: "Schema already up to date"
|
|
4371
4402
|
});
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4403
|
+
else {
|
|
4404
|
+
ctx.write(SCHEMA_LOCAL_PATH, schemaContent);
|
|
4405
|
+
results.push({
|
|
4406
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
4407
|
+
action: existingSchema ? "updated" : "created",
|
|
4408
|
+
description: "Copied Forgejo workflow schema from @bensandee/config"
|
|
4409
|
+
});
|
|
4379
4410
|
}
|
|
4411
|
+
results.push(writeSchemaToSettings(ctx));
|
|
4412
|
+
return results;
|
|
4380
4413
|
}
|
|
4381
4414
|
//#endregion
|
|
4382
4415
|
//#region src/generators/coverage-ci.ts
|
|
@@ -4395,6 +4428,10 @@ function coverageSteps(nodeVersionYaml) {
|
|
|
4395
4428
|
}
|
|
4396
4429
|
} },
|
|
4397
4430
|
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
4431
|
+
{ step: {
|
|
4432
|
+
name: "Build",
|
|
4433
|
+
run: "pnpm build"
|
|
4434
|
+
} },
|
|
4398
4435
|
{ step: {
|
|
4399
4436
|
name: "Run tests with coverage",
|
|
4400
4437
|
run: "pnpm test:coverage"
|
|
@@ -4674,7 +4711,7 @@ async function runGenerators(ctx) {
|
|
|
4674
4711
|
function generateMigratePrompt(results, config, detected) {
|
|
4675
4712
|
const sections = [];
|
|
4676
4713
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4677
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
4714
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.57.1 repo:sync\` on ${timestamp}_\n`);
|
|
4678
4715
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
4679
4716
|
if (changed.length > 0) {
|
|
4680
4717
|
const items = changed.map((r) => {
|
|
@@ -8088,7 +8125,7 @@ const out = (msg) => console.log(msg);
|
|
|
8088
8125
|
const main = defineCommand({
|
|
8089
8126
|
meta: {
|
|
8090
8127
|
name: "bst",
|
|
8091
|
-
version: "0.
|
|
8128
|
+
version: "0.57.1",
|
|
8092
8129
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
8093
8130
|
},
|
|
8094
8131
|
subCommands: {
|
|
@@ -8160,7 +8197,7 @@ const main = defineCommand({
|
|
|
8160
8197
|
})
|
|
8161
8198
|
}
|
|
8162
8199
|
});
|
|
8163
|
-
console.log(`@bensandee/tooling v0.
|
|
8200
|
+
console.log(`@bensandee/tooling v0.57.1`);
|
|
8164
8201
|
async function run() {
|
|
8165
8202
|
await runMain(main);
|
|
8166
8203
|
process.exit(process.exitCode ?? 0);
|