@bensandee/tooling 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/bin.mjs +356 -262
- package/package.json +6 -6
- package/tooling.schema.json +4 -0
package/README.md
CHANGED
|
@@ -78,6 +78,15 @@ Custom blocks are anchored to the nearest preceding non-blank line. When `repo:s
|
|
|
78
78
|
|
|
79
79
|
To skip a workflow file entirely, add `# @bensandee/tooling:ignore` as the first line — `repo:sync` will leave the file untouched.
|
|
80
80
|
|
|
81
|
+
### Repository setup
|
|
82
|
+
|
|
83
|
+
| Command | Description |
|
|
84
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `tooling setup:secrets` | Configure CI secrets (Forgejo or GitHub). Auto-detects platform from `package.json` repository URL. Flags: `--dry-run`, `--no-docker`. |
|
|
86
|
+
| `tooling forgejo:secrets` | Manage Forgejo Actions secrets directly. Subcommands: `set`, `list`, `delete`. |
|
|
87
|
+
|
|
88
|
+
`setup:secrets` detects the hosting platform from the `repository` field in `package.json`. For Forgejo repositories, it prompts for username and password, creates an access token with appropriate scopes, then sets `RELEASE_TOKEN` (and Docker secrets if Docker packages are detected). For GitHub repositories, it prompts for an existing Personal Access Token and uses `gh secret set` to configure secrets.
|
|
89
|
+
|
|
81
90
|
### Release management
|
|
82
91
|
|
|
83
92
|
| Command | Description |
|
package/dist/bin.mjs
CHANGED
|
@@ -625,7 +625,8 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
625
625
|
})).optional().meta({ description: "Docker package overrides (package name → config)" }),
|
|
626
626
|
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional().meta({ description: "Docker health check configuration or false to disable" }),
|
|
627
627
|
registryHost: z.string().optional().meta({ description: "Docker registry hostname (e.g. ghcr.io, code.example.com)" }),
|
|
628
|
-
registryNamespace: z.string().optional().meta({ description: "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)" })
|
|
628
|
+
registryNamespace: z.string().optional().meta({ description: "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)" }),
|
|
629
|
+
postReleaseHook: z.string().optional().meta({ description: "Override the post-release hook command (default: curl POST_RELEASE_HOOK_URL secret if set)" })
|
|
629
630
|
});
|
|
630
631
|
/** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
|
|
631
632
|
function loadToolingConfig(targetDir) {
|
|
@@ -1205,6 +1206,15 @@ function publishSteps(nodeVersionYaml, registryDerivable) {
|
|
|
1205
1206
|
];
|
|
1206
1207
|
}
|
|
1207
1208
|
const DockerMapSchema = z.object({ docker: z.record(z.string(), z.unknown()).optional() });
|
|
1209
|
+
const PostReleaseHookSchema = z.object({ postReleaseHook: z.string().optional() });
|
|
1210
|
+
/** Read the postReleaseHook command override from .tooling.json, if configured. */
|
|
1211
|
+
function getPostReleaseHookCommand(ctx) {
|
|
1212
|
+
const raw = ctx.read(".tooling.json");
|
|
1213
|
+
if (!raw) return void 0;
|
|
1214
|
+
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
1215
|
+
if (!result.success) return void 0;
|
|
1216
|
+
return result.data.postReleaseHook;
|
|
1217
|
+
}
|
|
1208
1218
|
/** Get names of packages that have Docker builds (by convention or .tooling.json config). */
|
|
1209
1219
|
function getDockerPackageNames(ctx) {
|
|
1210
1220
|
const names = [];
|
|
@@ -1330,9 +1340,9 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1330
1340
|
"oxlint": "1.57.0",
|
|
1331
1341
|
"prettier": "3.8.1",
|
|
1332
1342
|
"release-it": "19.2.4",
|
|
1333
|
-
"tsdown": "0.21.
|
|
1343
|
+
"tsdown": "0.21.6",
|
|
1334
1344
|
"typescript": "6.0.2",
|
|
1335
|
-
"vitest": "
|
|
1345
|
+
"vitest": "4.1.2",
|
|
1336
1346
|
"pnpm": "10.33.0"
|
|
1337
1347
|
}["@types/node"],
|
|
1338
1348
|
tsdown: {
|
|
@@ -1346,9 +1356,9 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1346
1356
|
"oxlint": "1.57.0",
|
|
1347
1357
|
"prettier": "3.8.1",
|
|
1348
1358
|
"release-it": "19.2.4",
|
|
1349
|
-
"tsdown": "0.21.
|
|
1359
|
+
"tsdown": "0.21.6",
|
|
1350
1360
|
"typescript": "6.0.2",
|
|
1351
|
-
"vitest": "
|
|
1361
|
+
"vitest": "4.1.2",
|
|
1352
1362
|
"pnpm": "10.33.0"
|
|
1353
1363
|
}["tsdown"],
|
|
1354
1364
|
typescript: {
|
|
@@ -1362,9 +1372,9 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1362
1372
|
"oxlint": "1.57.0",
|
|
1363
1373
|
"prettier": "3.8.1",
|
|
1364
1374
|
"release-it": "19.2.4",
|
|
1365
|
-
"tsdown": "0.21.
|
|
1375
|
+
"tsdown": "0.21.6",
|
|
1366
1376
|
"typescript": "6.0.2",
|
|
1367
|
-
"vitest": "
|
|
1377
|
+
"vitest": "4.1.2",
|
|
1368
1378
|
"pnpm": "10.33.0"
|
|
1369
1379
|
}["typescript"],
|
|
1370
1380
|
vitest: {
|
|
@@ -1378,9 +1388,9 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1378
1388
|
"oxlint": "1.57.0",
|
|
1379
1389
|
"prettier": "3.8.1",
|
|
1380
1390
|
"release-it": "19.2.4",
|
|
1381
|
-
"tsdown": "0.21.
|
|
1391
|
+
"tsdown": "0.21.6",
|
|
1382
1392
|
"typescript": "6.0.2",
|
|
1383
|
-
"vitest": "
|
|
1393
|
+
"vitest": "4.1.2",
|
|
1384
1394
|
"pnpm": "10.33.0"
|
|
1385
1395
|
}["vitest"]
|
|
1386
1396
|
};
|
|
@@ -1397,9 +1407,9 @@ const ROOT_DEV_DEPS = {
|
|
|
1397
1407
|
"oxlint": "1.57.0",
|
|
1398
1408
|
"prettier": "3.8.1",
|
|
1399
1409
|
"release-it": "19.2.4",
|
|
1400
|
-
"tsdown": "0.21.
|
|
1410
|
+
"tsdown": "0.21.6",
|
|
1401
1411
|
"typescript": "6.0.2",
|
|
1402
|
-
"vitest": "
|
|
1412
|
+
"vitest": "4.1.2",
|
|
1403
1413
|
"pnpm": "10.33.0"
|
|
1404
1414
|
}["knip"],
|
|
1405
1415
|
lefthook: {
|
|
@@ -1413,9 +1423,9 @@ const ROOT_DEV_DEPS = {
|
|
|
1413
1423
|
"oxlint": "1.57.0",
|
|
1414
1424
|
"prettier": "3.8.1",
|
|
1415
1425
|
"release-it": "19.2.4",
|
|
1416
|
-
"tsdown": "0.21.
|
|
1426
|
+
"tsdown": "0.21.6",
|
|
1417
1427
|
"typescript": "6.0.2",
|
|
1418
|
-
"vitest": "
|
|
1428
|
+
"vitest": "4.1.2",
|
|
1419
1429
|
"pnpm": "10.33.0"
|
|
1420
1430
|
}["lefthook"],
|
|
1421
1431
|
oxlint: {
|
|
@@ -1429,9 +1439,9 @@ const ROOT_DEV_DEPS = {
|
|
|
1429
1439
|
"oxlint": "1.57.0",
|
|
1430
1440
|
"prettier": "3.8.1",
|
|
1431
1441
|
"release-it": "19.2.4",
|
|
1432
|
-
"tsdown": "0.21.
|
|
1442
|
+
"tsdown": "0.21.6",
|
|
1433
1443
|
"typescript": "6.0.2",
|
|
1434
|
-
"vitest": "
|
|
1444
|
+
"vitest": "4.1.2",
|
|
1435
1445
|
"pnpm": "10.33.0"
|
|
1436
1446
|
}["oxlint"]
|
|
1437
1447
|
};
|
|
@@ -1470,9 +1480,9 @@ function addReleaseDeps(deps, config) {
|
|
|
1470
1480
|
"oxlint": "1.57.0",
|
|
1471
1481
|
"prettier": "3.8.1",
|
|
1472
1482
|
"release-it": "19.2.4",
|
|
1473
|
-
"tsdown": "0.21.
|
|
1483
|
+
"tsdown": "0.21.6",
|
|
1474
1484
|
"typescript": "6.0.2",
|
|
1475
|
-
"vitest": "
|
|
1485
|
+
"vitest": "4.1.2",
|
|
1476
1486
|
"pnpm": "10.33.0"
|
|
1477
1487
|
}["release-it"];
|
|
1478
1488
|
if (config.structure === "monorepo") deps["@release-it/bumper"] = {
|
|
@@ -1486,9 +1496,9 @@ function addReleaseDeps(deps, config) {
|
|
|
1486
1496
|
"oxlint": "1.57.0",
|
|
1487
1497
|
"prettier": "3.8.1",
|
|
1488
1498
|
"release-it": "19.2.4",
|
|
1489
|
-
"tsdown": "0.21.
|
|
1499
|
+
"tsdown": "0.21.6",
|
|
1490
1500
|
"typescript": "6.0.2",
|
|
1491
|
-
"vitest": "
|
|
1501
|
+
"vitest": "4.1.2",
|
|
1492
1502
|
"pnpm": "10.33.0"
|
|
1493
1503
|
}["@release-it/bumper"];
|
|
1494
1504
|
break;
|
|
@@ -1504,9 +1514,9 @@ function addReleaseDeps(deps, config) {
|
|
|
1504
1514
|
"oxlint": "1.57.0",
|
|
1505
1515
|
"prettier": "3.8.1",
|
|
1506
1516
|
"release-it": "19.2.4",
|
|
1507
|
-
"tsdown": "0.21.
|
|
1517
|
+
"tsdown": "0.21.6",
|
|
1508
1518
|
"typescript": "6.0.2",
|
|
1509
|
-
"vitest": "
|
|
1519
|
+
"vitest": "4.1.2",
|
|
1510
1520
|
"pnpm": "10.33.0"
|
|
1511
1521
|
}["commit-and-tag-version"];
|
|
1512
1522
|
break;
|
|
@@ -1522,9 +1532,9 @@ function addReleaseDeps(deps, config) {
|
|
|
1522
1532
|
"oxlint": "1.57.0",
|
|
1523
1533
|
"prettier": "3.8.1",
|
|
1524
1534
|
"release-it": "19.2.4",
|
|
1525
|
-
"tsdown": "0.21.
|
|
1535
|
+
"tsdown": "0.21.6",
|
|
1526
1536
|
"typescript": "6.0.2",
|
|
1527
|
-
"vitest": "
|
|
1537
|
+
"vitest": "4.1.2",
|
|
1528
1538
|
"pnpm": "10.33.0"
|
|
1529
1539
|
}["@changesets/cli"];
|
|
1530
1540
|
break;
|
|
@@ -1548,8 +1558,8 @@ async function generatePackageJson(ctx) {
|
|
|
1548
1558
|
}
|
|
1549
1559
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1550
1560
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1551
|
-
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.
|
|
1552
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1561
|
+
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.1";
|
|
1562
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.42.0";
|
|
1553
1563
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1554
1564
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1555
1565
|
"@changesets/cli": "2.30.0",
|
|
@@ -1562,9 +1572,9 @@ async function generatePackageJson(ctx) {
|
|
|
1562
1572
|
"oxlint": "1.57.0",
|
|
1563
1573
|
"prettier": "3.8.1",
|
|
1564
1574
|
"release-it": "19.2.4",
|
|
1565
|
-
"tsdown": "0.21.
|
|
1575
|
+
"tsdown": "0.21.6",
|
|
1566
1576
|
"typescript": "6.0.2",
|
|
1567
|
-
"vitest": "
|
|
1577
|
+
"vitest": "4.1.2",
|
|
1568
1578
|
"pnpm": "10.33.0"
|
|
1569
1579
|
}["oxfmt"];
|
|
1570
1580
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
|
|
@@ -1578,9 +1588,9 @@ async function generatePackageJson(ctx) {
|
|
|
1578
1588
|
"oxlint": "1.57.0",
|
|
1579
1589
|
"prettier": "3.8.1",
|
|
1580
1590
|
"release-it": "19.2.4",
|
|
1581
|
-
"tsdown": "0.21.
|
|
1591
|
+
"tsdown": "0.21.6",
|
|
1582
1592
|
"typescript": "6.0.2",
|
|
1583
|
-
"vitest": "
|
|
1593
|
+
"vitest": "4.1.2",
|
|
1584
1594
|
"pnpm": "10.33.0"
|
|
1585
1595
|
}["prettier"];
|
|
1586
1596
|
addReleaseDeps(devDeps, ctx.config);
|
|
@@ -1662,9 +1672,9 @@ async function generatePackageJson(ctx) {
|
|
|
1662
1672
|
"oxlint": "1.57.0",
|
|
1663
1673
|
"prettier": "3.8.1",
|
|
1664
1674
|
"release-it": "19.2.4",
|
|
1665
|
-
"tsdown": "0.21.
|
|
1675
|
+
"tsdown": "0.21.6",
|
|
1666
1676
|
"typescript": "6.0.2",
|
|
1667
|
-
"vitest": "
|
|
1677
|
+
"vitest": "4.1.2",
|
|
1668
1678
|
"pnpm": "10.33.0"
|
|
1669
1679
|
}["pnpm"]}`
|
|
1670
1680
|
};
|
|
@@ -2156,6 +2166,185 @@ async function generateGitignore(ctx) {
|
|
|
2156
2166
|
};
|
|
2157
2167
|
}
|
|
2158
2168
|
//#endregion
|
|
2169
|
+
//#region src/generators/release-ci.ts
|
|
2170
|
+
const DEFAULT_POST_RELEASE_COMMAND = "if [ -n \"$POST_RELEASE_HOOK_URL\" ]; then curl -fsS \"$POST_RELEASE_HOOK_URL\"; fi";
|
|
2171
|
+
/** Step that runs after release, gated on the POST_RELEASE_HOOK_URL secret being set. */
|
|
2172
|
+
function postReleaseHookStep(command) {
|
|
2173
|
+
return { step: {
|
|
2174
|
+
name: "Post-release hook",
|
|
2175
|
+
env: { POST_RELEASE_HOOK_URL: actionsExpr("secrets.POST_RELEASE_HOOK_URL") },
|
|
2176
|
+
run: command ?? DEFAULT_POST_RELEASE_COMMAND
|
|
2177
|
+
} };
|
|
2178
|
+
}
|
|
2179
|
+
/** Common setup steps shared by release-it and simple strategies. */
|
|
2180
|
+
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
2181
|
+
const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
|
|
2182
|
+
return [
|
|
2183
|
+
{ step: {
|
|
2184
|
+
uses: "actions/checkout@v6",
|
|
2185
|
+
with: { "fetch-depth": 0 }
|
|
2186
|
+
} },
|
|
2187
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
2188
|
+
{ step: {
|
|
2189
|
+
uses: "actions/setup-node@v6",
|
|
2190
|
+
with: {
|
|
2191
|
+
...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2192
|
+
cache: "pnpm",
|
|
2193
|
+
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
2194
|
+
}
|
|
2195
|
+
} },
|
|
2196
|
+
{ step: { run: "pnpm install --frozen-lockfile" } }
|
|
2197
|
+
];
|
|
2198
|
+
}
|
|
2199
|
+
function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
|
|
2200
|
+
const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
|
|
2201
|
+
const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
|
|
2202
|
+
return [...commonSteps(nodeVersionYaml, publishesNpm), { step: {
|
|
2203
|
+
run: "pnpm release-it --ci",
|
|
2204
|
+
env: {
|
|
2205
|
+
...tokenEnv,
|
|
2206
|
+
...npmEnv,
|
|
2207
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2208
|
+
}
|
|
2209
|
+
} }];
|
|
2210
|
+
}
|
|
2211
|
+
/** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
|
|
2212
|
+
function simpleWorkflowDispatchTrigger() {
|
|
2213
|
+
return { workflow_dispatch: { inputs: {
|
|
2214
|
+
bump: {
|
|
2215
|
+
description: "Version bump type (default: conventional-commits auto-detect)",
|
|
2216
|
+
required: false,
|
|
2217
|
+
type: "choice",
|
|
2218
|
+
default: "auto",
|
|
2219
|
+
options: [
|
|
2220
|
+
"auto",
|
|
2221
|
+
"major",
|
|
2222
|
+
"minor",
|
|
2223
|
+
"patch",
|
|
2224
|
+
"first-release"
|
|
2225
|
+
]
|
|
2226
|
+
},
|
|
2227
|
+
prerelease: {
|
|
2228
|
+
description: "Create a prerelease with the given tag (e.g., beta, alpha)",
|
|
2229
|
+
required: false,
|
|
2230
|
+
type: "string"
|
|
2231
|
+
}
|
|
2232
|
+
} } };
|
|
2233
|
+
}
|
|
2234
|
+
/** Build the release:simple run command with conditional flags from workflow inputs. */
|
|
2235
|
+
function simpleReleaseCommand() {
|
|
2236
|
+
return [
|
|
2237
|
+
"FLAGS=",
|
|
2238
|
+
`case "${actionsExpr("inputs.bump")}" in`,
|
|
2239
|
+
" major|minor|patch) FLAGS=\"$FLAGS --release-as " + actionsExpr("inputs.bump") + "\" ;;",
|
|
2240
|
+
" first-release) FLAGS=\"$FLAGS --first-release\" ;;",
|
|
2241
|
+
"esac",
|
|
2242
|
+
`if [ -n "${actionsExpr("inputs.prerelease")}" ]; then FLAGS="$FLAGS --prerelease ${actionsExpr("inputs.prerelease")}"; fi`,
|
|
2243
|
+
"pnpm exec bst release:simple $FLAGS"
|
|
2244
|
+
].join("\n");
|
|
2245
|
+
}
|
|
2246
|
+
/** Build the docker publish env block, omitting registry vars when derivable at runtime. */
|
|
2247
|
+
function dockerPublishEnv(registryDerivable) {
|
|
2248
|
+
return {
|
|
2249
|
+
...registryDerivable ? {} : {
|
|
2250
|
+
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
2251
|
+
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")
|
|
2252
|
+
},
|
|
2253
|
+
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
2254
|
+
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
2255
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2259
|
+
const releaseStep = { step: {
|
|
2260
|
+
name: "Release",
|
|
2261
|
+
env: {
|
|
2262
|
+
...ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
2263
|
+
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2264
|
+
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2265
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2266
|
+
},
|
|
2267
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2268
|
+
},
|
|
2269
|
+
run: simpleReleaseCommand()
|
|
2270
|
+
} };
|
|
2271
|
+
const dockerStep = { step: {
|
|
2272
|
+
name: "Publish Docker images",
|
|
2273
|
+
if: "success()",
|
|
2274
|
+
env: dockerPublishEnv(registryDerivable),
|
|
2275
|
+
run: "pnpm exec bst docker:publish"
|
|
2276
|
+
} };
|
|
2277
|
+
return [
|
|
2278
|
+
...commonSteps(nodeVersionYaml, publishesNpm),
|
|
2279
|
+
releaseStep,
|
|
2280
|
+
...hasDocker ? [dockerStep] : []
|
|
2281
|
+
];
|
|
2282
|
+
}
|
|
2283
|
+
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2284
|
+
switch (strategy) {
|
|
2285
|
+
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
2286
|
+
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2287
|
+
default: return null;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
async function generateReleaseCi(ctx) {
|
|
2291
|
+
const filePath = "release-ci";
|
|
2292
|
+
if (ctx.config.releaseStrategy === "none" || ctx.config.ci === "none") return {
|
|
2293
|
+
filePath,
|
|
2294
|
+
action: "skipped",
|
|
2295
|
+
description: "Release CI workflow not applicable"
|
|
2296
|
+
};
|
|
2297
|
+
const publishesNpm = ctx.config.publishNpm === true;
|
|
2298
|
+
if (ctx.config.releaseStrategy === "changesets") return {
|
|
2299
|
+
filePath,
|
|
2300
|
+
action: "skipped",
|
|
2301
|
+
description: "Release step included in CI workflow"
|
|
2302
|
+
};
|
|
2303
|
+
const isGitHub = ctx.config.ci === "github";
|
|
2304
|
+
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
2305
|
+
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2306
|
+
const hasDocker = hasDockerPackages(ctx);
|
|
2307
|
+
const registryDerivable = hasRepositoryUrl(ctx);
|
|
2308
|
+
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2309
|
+
if (!steps) return {
|
|
2310
|
+
filePath,
|
|
2311
|
+
action: "skipped",
|
|
2312
|
+
description: "Release CI workflow not applicable"
|
|
2313
|
+
};
|
|
2314
|
+
steps.push(postReleaseHookStep(getPostReleaseHookCommand(ctx)));
|
|
2315
|
+
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
2316
|
+
const content = buildWorkflowYaml({
|
|
2317
|
+
ci: ctx.config.ci,
|
|
2318
|
+
name: "Release",
|
|
2319
|
+
on,
|
|
2320
|
+
...isGitHub && { permissions: { contents: "write" } },
|
|
2321
|
+
jobName: "release",
|
|
2322
|
+
steps
|
|
2323
|
+
});
|
|
2324
|
+
const alreadyExists = ctx.exists(workflowPath);
|
|
2325
|
+
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
2326
|
+
if (existing) {
|
|
2327
|
+
if (isToolingIgnored(existing)) return {
|
|
2328
|
+
filePath: workflowPath,
|
|
2329
|
+
action: "skipped",
|
|
2330
|
+
description: "Release workflow has ignore comment"
|
|
2331
|
+
};
|
|
2332
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
2333
|
+
filePath: workflowPath,
|
|
2334
|
+
action: "skipped",
|
|
2335
|
+
description: "Release workflow already up to date"
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
const withDevPath = preserveDevBinaryPath(content, existing);
|
|
2339
|
+
const final = existing ? insertCustomBlocks(withDevPath, extractCustomBlocks(existing)) : withDevPath;
|
|
2340
|
+
ctx.write(workflowPath, final);
|
|
2341
|
+
return {
|
|
2342
|
+
filePath: workflowPath,
|
|
2343
|
+
action: alreadyExists ? "updated" : "created",
|
|
2344
|
+
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2159
2348
|
//#region src/generators/ci.ts
|
|
2160
2349
|
/** Build the release step for the check job (changesets strategy). */
|
|
2161
2350
|
function changesetsReleaseStep(ci, publishesNpm) {
|
|
@@ -2226,7 +2415,12 @@ async function generateCi(ctx) {
|
|
|
2226
2415
|
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
2227
2416
|
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2228
2417
|
const publishesNpm = ctx.config.publishNpm === true;
|
|
2229
|
-
const
|
|
2418
|
+
const hookCommand = getPostReleaseHookCommand(ctx);
|
|
2419
|
+
const steps = [
|
|
2420
|
+
...checkSteps(nodeVersionYaml, isChangesets && publishesNpm),
|
|
2421
|
+
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : [],
|
|
2422
|
+
...isChangesets ? [postReleaseHookStep(hookCommand)] : []
|
|
2423
|
+
];
|
|
2230
2424
|
const content = buildWorkflowYaml({
|
|
2231
2425
|
ci: ctx.config.ci,
|
|
2232
2426
|
name: "CI",
|
|
@@ -2473,7 +2667,7 @@ function writeIfChanged(ctx, filePath, content, label) {
|
|
|
2473
2667
|
};
|
|
2474
2668
|
}
|
|
2475
2669
|
async function generateClaudeHooks(ctx) {
|
|
2476
|
-
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.5.0\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport{a as e,i as t,n,o as r,r as i,t as a}from\"./file-rules-cP7-5vh6.mjs\";import*as o from\"node:fs\";import{existsSync as s,readFileSync as c,readdirSync as l,statSync as u}from\"node:fs\";import*as d from\"node:path\";import{dirname as ee,join as f,resolve as p}from\"node:path\";import{fileURLToPath as te}from\"node:url\";Object.freeze({status:`aborted`});function m(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var h=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const g={};function _(e){return e&&Object.assign(g,e),g}function ne(e,t){return typeof t==`bigint`?t.toString():t}function re(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ie(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const v=Symbol(`evaluating`);function y(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==v)return r===void 0&&(r=v,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const b=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function x(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(x(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(x(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function oe(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function S(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function se(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function C(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function w(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function T(e){return typeof e==`string`?e:e?.message}function E(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=T(e.inst?._zod.def?.error?.(e))??T(t?.error?.(e))??T(n.customError?.(e))??T(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const D=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ne,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},ce=m(`$ZodError`,D),O=m(`$ZodError`,D,{Parent:Error}),le=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new h;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(O),ue=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(O),k=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new h;return a.issues.length?{success:!1,error:new(e??ce)(a.issues.map(e=>E(e,i,_())))}:{success:!0,data:a.value}})(O),A=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>E(e,i,_())))}:{success:!0,data:a.value}})(O),de=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},fe=/^-?\\d+(?:\\.\\d+)?$/,pe={major:4,minor:3,patch:6},j=m(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=pe;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=C(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new h;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=C(e,t))});else{if(e.issues.length===t)continue;r||=C(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(C(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new h;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new h;return o.then(e=>t(e,r,a))}return t(o,r,a)}}y(e,`~standard`,()=>({validate:t=>{try{let n=k(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return A(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),me=m(`$ZodString`,(e,t)=>{j.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??de(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),he=m(`$ZodUnknown`,(e,t)=>{j.init(e,t),e._zod.parse=e=>e});function M(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...w(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function ge(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=se(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function _e(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>M(e,n,i,t,u))):M(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const ve=m(`$ZodObject`,(e,t)=>{if(j.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=re(()=>ge(t));y(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=x,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>M(n,t,e,s,r))):M(i,t,e,s,r)}return i?_e(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),ye=m(`$ZodRecord`,(e,t)=>{j.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ae(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...w(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...w(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&fe.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>E(e,r,_())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...w(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...w(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function N(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const be=m(`$ZodOptional`,(e,t)=>{j.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,y(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),y(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ie(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>N(t,e.value)):N(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var P,xe=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function Se(){return new xe}(P=globalThis).__zod_globalRegistry??(P.__zod_globalRegistry=Se()),globalThis.__zod_globalRegistry;function Ce(e,t){return new e({type:`string`,...S(t)})}function we(e){return new e({type:`unknown`})}const F=m(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);j.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>le(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>k(e,t,n),e.parseAsync=async(t,n)=>ue(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>A(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>oe(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),Te=m(`ZodMiniString`,(e,t)=>{me.init(e,t),F.init(e,t)});function I(e){return Ce(Te,e)}const Ee=m(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),F.init(e,t)});function L(){return we(Ee)}const De=m(`ZodMiniObject`,(e,t)=>{ve.init(e,t),F.init(e,t),y(e,`shape`,()=>t.shape)});function R(e,t){return new De({type:`object`,shape:e??{},...S(t)})}const Oe=m(`ZodMiniRecord`,(e,t)=>{ye.init(e,t),F.init(e,t)});function z(e,t,n){return new Oe({type:`record`,keyType:e,valueType:t,...S(n)})}const ke=m(`ZodMiniOptional`,(e,t)=>{be.init(e,t),F.init(e,t)});function B(e){return new ke({type:`optional`,innerType:e})}const Ae=R({session_id:I(),transcript_path:I(),cwd:I(),permission_mode:I(),hook_event_name:I(),tool_name:I(),tool_input:z(I(),L())});async function je(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Ae.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Me=R({command:I(),description:B(I())}),Ne=R({file_path:I()}),Pe=R({pattern:I(),path:B(I())});function Fe(e){let t=Me.safeParse(e);return t.success?t.data:null}function Ie(e){let t=Ne.safeParse(e);return t.success?t.data:null}function Le(e){let t=Pe.safeParse(e);return t.success?t.data:null}function V(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function Re(){let e=d.dirname(te(import.meta.url));return d.join(e,`bst-hook.audit.local.jsonl`)}function ze(e){try{if(o.statSync(e).size>1048576){let t=`${e}.1`;o.renameSync(e,t)}}catch{}}let Be=!1;function Ve(e){try{let t=Re();Be||=(ze(t),!0);let n={timestamp:new Date().toISOString(),...e};o.appendFileSync(t,JSON.stringify(n)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function H(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function He(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){U(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){U(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){U(t,n,`||`),n=``,r+=2;continue}if(o===`|`){U(t,n,`|`),n=``,r++;continue}if(o===`;`){U(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function U(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const W=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ue(e,t){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!W.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!W.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const We=R({scripts:z(I(),L())});function G(e,t){let n=Qe(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function Ge(e,t,n){let r=Je(t,n);if(!r)return null;let i=G(e,r);return i?{...i,pkgDir:r}:null}function Ke(e,t){let n=K(t);if(!n)return null;let r=[];for(let t of n){let n=G(e,t);n&&r.push({...n,pkgDir:t})}return r}function qe(e){let t=p(e);for(;;){if(s(f(t,`pnpm-workspace.yaml`)))return t;let e=ee(t);if(e===t)return null;t=e}}function K(e){let t=Ye(e);if(!t)return null;let n=[];for(let r of t){if(!r.endsWith(`/*`))continue;let t=p(e,r.slice(0,-2)),i;try{i=l(t)}catch{continue}for(let e of i){let r=f(t,e);try{if(!u(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function Je(e,t){let n=K(t);if(!n)return null;for(let t of n){let n=Ze(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function Ye(e){try{let t=c(f(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),n=t.findIndex(e=>/^packages\\s*:/.test(e));if(n===-1)return null;let r=[];for(let e=n+1;e<t.length;e++){let n=t[e],i=/^\\s+-\\s+(.+)$/.exec(n);if(!i)break;r.push(i[1].trim().replace(/['\"`]/g,``))}return r.length>0?r:null}catch{return null}}const Xe=R({name:I()});function Ze(e){try{let t=c(f(e,`package.json`),`utf-8`),n=Xe.safeParse(JSON.parse(t));return n.success?n.data.name:null}catch{return null}}function Qe(e){try{let t=c(f(e,`package.json`),`utf-8`),n=We.safeParse(JSON.parse(t));return n.success?n.data.scripts:null}catch{return null}}const $e=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),et=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function q(e){return $e.has(e)?!0:et.some(t=>e.startsWith(t))}function tt(e,t){let n=H(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return d.isAbsolute(i)?i:d.resolve(t.cwd,i)}function J(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=He(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=tt(e.command,o);t&&(o={...o,cwd:t})}let t=nt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function Y(e,t){if(e.pre){let n=J(e.pre,t);if(n.action!==`allow`)return n}let n=J(e.main,t);if(n.action!==`allow`)return n;if(e.post){let n=J(e.post,t);if(n.action!==`allow`)return n}return`allow`}function nt(n,r){let i=n.trim();if(i.startsWith(`(`)&&i.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let a=H(n);if(!a.ok)return{action:`passthrough`,rule:null,reason:a.error};let o=a.tokens;if(o.length===0)return{action:`passthrough`,rule:null,reason:null};let s=0;for(;s<o.length&&/^\\w+=/.test(o[s]);){let e=o[s].split(`=`)[0];if(q(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};s++}if(s>=o.length)return{action:`allow`,rule:`env-only`,reason:null};o=o.slice(s);let c=Ue(o,n);if(!c.safe)return{action:`passthrough`,rule:null,reason:c.reason};for(let e=0;e<o.length;e++){let t=o[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(o[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let l=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);o=o.filter((e,t)=>!(l.has(e)||(e===`>`||e===`>>`)&&o[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(o[t-1]===`>`||o[t-1]===`>>`)));let u=d.basename(o[0]);if(u===`command`&&o.length>1)return J(o.slice(1).join(` `),{...r,depth:r.depth+1});if(u===`env`){let e=1;for(;e<o.length&&/^\\w+=/.test(o[e]);){let t=o[e].split(`=`)[0];if(q(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=o.length?{action:`allow`,rule:`env-only`,reason:null}:J(o.slice(e).join(` `),{...r,depth:r.depth+1})}if(u===`pnpm`)return rt(o,r);if(u===`npm`)return at(o);if(u===`bash`||u===`sh`||u===`zsh`)return ct(o,r);if(u===`node`&&(o.includes(`-e`)||o.includes(`-p`)||o.includes(`--eval`)||o.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(u===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let t of e)if(t.match.test(u)&&(!t.when||t.when(o,r)))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let e of t)if(e.match.test(u)&&(!e.when||e.when(o,r)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${u}`}}function rt(e,n){let r=e.slice(1),a,o=-1,s=!1,c=!1,l;for(let t=0;t<r.length;t++){let u=r[t];if(u===`--filter`||u.startsWith(`--filter=`))return ot(e,r,t,n);if(u===`-r`||u===`--recursive`){s=!0;continue}if(u===`-w`||u===`--workspace-root`){c=!0;continue}if(u===`-C`||u===`--dir`){l=r[++t];continue}if(u.startsWith(`-C=`)||u.startsWith(`--dir=`)){l=u.split(`=`,2)[1];continue}if(!(u.startsWith(`-`)&&!i.has(u))){a=u,o=t;break}}if(!a)return{action:`passthrough`,rule:null,reason:null};if(a===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(a===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(a===`exec`){let e=r.slice(o+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:J(e.join(` `),{...n,depth:n.depth+1})}let u;if(a===`run`){let e=r[o+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};u=e}else if(!i.has(a))u=a;else{for(let r of t)if(r.match.test(`pnpm`)&&(!r.when||r.when(e,n)))return{action:`allow`,rule:r.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(s)return it(u,n);if(c){let e=qe(n.cwd);return e?X(u,{...n,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(l){let e=d.resolve(n.cwd,l);return X(u,{...n,cwd:e})}return X(u,n)}function X(e,t){let n=G(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=Y(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function it(e,t){let n=Ke(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=Y(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function at(e){for(let n of t)if(n.match.test(`npm`)&&(!n.when||n.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:n.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:r(e)}}function ot(e,t,n,r){let a=t[n],o,s;if(a.startsWith(`--filter=`))o=a.slice(9),s=n+1;else{if(o=t[n+1],!o||o.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};s=n+2}if(!o)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let c;for(let e=s;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!i.has(n))){c=n;break}}if(!c)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(c===`install`||c===`i`)return t.slice(t.indexOf(c)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(c===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(c===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(c))return{action:`allow`,rule:`pnpm`,reason:null};if(c===`exec`){let e=t.indexOf(`exec`,s),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:J(n.join(` `),{...r,depth:r.depth+1})}let l=c===`run`?t[t.indexOf(`run`,s)+1]:c;if(!l||l.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(i.has(l)&&l!==`run`)return{action:`passthrough`,rule:null,reason:null};let u=Ge(l,o,r.cwd);if(!u)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let d=Y(u,{...r,depth:r.depth+1,cwd:u.pkgDir??r.cwd});return d===`allow`?{action:`allow`,rule:`pnpm-filter:${o}:${l}`,reason:null}:d}const st=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function ct(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||st.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?J(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const lt=new Set([`Read`,`Write`,`Edit`]),ut=new Set([`Glob`,`Grep`]);function dt(e,t,n){return lt.has(e)?Ie(t)?.file_path??null:ut.has(e)?Le(t)?.path??n:null}function ft(e,t,r){let i=dt(e,t,r.cwd);if(i===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of n)if(t.tools.has(e)&&t.when(i,r))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of a)if(t.tools.has(e)&&t.when(i,r))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await je();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);let Q,$;if(Z.tool_name===`Bash`){let e=Fe(Z.tool_input);e||process.exit(0),Q=J(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=ft(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}Ve({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?V(`allow`):Q.action===`deny`&&V(`deny`,Q.reason);export{};", "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- **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- **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")];
|
|
2670
|
+
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.0\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport{a as e,i as t,n,o as r,r as i,t as a}from\"./file-rules-RB-fEyic.mjs\";import*as o from\"node:fs\";import{existsSync as s,readFileSync as c,readdirSync as l,statSync as u}from\"node:fs\";import*as d from\"node:path\";import{dirname as ee,join as f,resolve as p}from\"node:path\";import{fileURLToPath as te}from\"node:url\";Object.freeze({status:`aborted`});function m(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var h=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const g={};function _(e){return e&&Object.assign(g,e),g}function ne(e,t){return typeof t==`bigint`?t.toString():t}function re(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ie(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const v=Symbol(`evaluating`);function y(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==v)return r===void 0&&(r=v,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const b=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function x(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(x(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(x(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function oe(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function S(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function se(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function C(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function w(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function T(e){return typeof e==`string`?e:e?.message}function E(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=T(e.inst?._zod.def?.error?.(e))??T(t?.error?.(e))??T(n.customError?.(e))??T(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const ce=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ne,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},le=m(`$ZodError`,ce),D=m(`$ZodError`,ce,{Parent:Error}),ue=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new h;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(D),de=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(D),O=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new h;return a.issues.length?{success:!1,error:new(e??le)(a.issues.map(e=>E(e,i,_())))}:{success:!0,data:a.value}})(D),k=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>E(e,i,_())))}:{success:!0,data:a.value}})(D),fe=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},pe=/^-?\\d+(?:\\.\\d+)?$/,me={major:4,minor:3,patch:6},A=m(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=me;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=C(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new h;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=C(e,t))});else{if(e.issues.length===t)continue;r||=C(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(C(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new h;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new h;return o.then(e=>t(e,r,a))}return t(o,r,a)}}y(e,`~standard`,()=>({validate:t=>{try{let n=O(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return k(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),he=m(`$ZodString`,(e,t)=>{A.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??fe(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),ge=m(`$ZodUnknown`,(e,t)=>{A.init(e,t),e._zod.parse=e=>e});function j(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...w(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function _e(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=se(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function ve(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>j(e,n,i,t,u))):j(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const ye=m(`$ZodObject`,(e,t)=>{if(A.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=re(()=>_e(t));y(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=x,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>j(n,t,e,s,r))):j(i,t,e,s,r)}return i?ve(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),be=m(`$ZodRecord`,(e,t)=>{A.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ae(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...w(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...w(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&pe.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>E(e,r,_())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...w(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...w(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function M(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const xe=m(`$ZodOptional`,(e,t)=>{A.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,y(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),y(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ie(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>M(t,e.value)):M(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var N,Se=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function Ce(){return new Se}(N=globalThis).__zod_globalRegistry??(N.__zod_globalRegistry=Ce()),globalThis.__zod_globalRegistry;function we(e,t){return new e({type:`string`,...S(t)})}function Te(e){return new e({type:`unknown`})}const P=m(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);A.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>ue(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>O(e,t,n),e.parseAsync=async(t,n)=>de(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>k(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>oe(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),Ee=m(`ZodMiniString`,(e,t)=>{he.init(e,t),P.init(e,t)});function F(e){return we(Ee,e)}const De=m(`ZodMiniUnknown`,(e,t)=>{ge.init(e,t),P.init(e,t)});function I(){return Te(De)}const Oe=m(`ZodMiniObject`,(e,t)=>{ye.init(e,t),P.init(e,t),y(e,`shape`,()=>t.shape)});function L(e,t){return new Oe({type:`object`,shape:e??{},...S(t)})}const ke=m(`ZodMiniRecord`,(e,t)=>{be.init(e,t),P.init(e,t)});function R(e,t,n){return new ke({type:`record`,keyType:e,valueType:t,...S(n)})}const Ae=m(`ZodMiniOptional`,(e,t)=>{xe.init(e,t),P.init(e,t)});function z(e){return new Ae({type:`optional`,innerType:e})}const je=L({session_id:F(),transcript_path:F(),cwd:F(),permission_mode:F(),hook_event_name:F(),tool_name:F(),tool_input:R(F(),I())});async function Me(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=je.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Ne=L({command:F(),description:z(F())}),Pe=L({file_path:F()}),Fe=L({pattern:F(),path:z(F())});function Ie(e){let t=Ne.safeParse(e);return t.success?t.data:null}function Le(e){let t=Pe.safeParse(e);return t.success?t.data:null}function Re(e){let t=Fe.safeParse(e);return t.success?t.data:null}function B(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function ze(){let e=d.dirname(te(import.meta.url));return d.join(e,`bst-hook.audit.local.jsonl`)}function Be(e){try{if(o.statSync(e).size>1048576){let t=`${e}.1`;o.renameSync(e,t)}}catch{}}let Ve=!1;function He(e){try{let t=ze();Ve||=(Be(t),!0);let n={timestamp:new Date().toISOString(),...e};o.appendFileSync(t,JSON.stringify(n)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function V(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function Ue(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){H(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){H(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){H(t,n,`||`),n=``,r+=2;continue}if(o===`|`){H(t,n,`|`),n=``,r++;continue}if(o===`;`){H(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function H(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const U=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function We(e,t){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!U.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!U.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const Ge=L({scripts:R(F(),I())});function W(e,t){let n=$e(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function Ke(e,t,n){let r=Ye(t,n);if(!r)return null;let i=W(e,r);return i?{...i,pkgDir:r}:null}function qe(e,t){let n=G(t);if(!n)return null;let r=[];for(let t of n){let n=W(e,t);n&&r.push({...n,pkgDir:t})}return r}function Je(e){let t=p(e);for(;;){if(s(f(t,`pnpm-workspace.yaml`)))return t;let e=ee(t);if(e===t)return null;t=e}}function G(e){let t=Xe(e);if(!t)return null;let n=[];for(let r of t){if(!r.endsWith(`/*`))continue;let t=p(e,r.slice(0,-2)),i;try{i=l(t)}catch{continue}for(let e of i){let r=f(t,e);try{if(!u(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function Ye(e,t){let n=G(t);if(!n)return null;for(let t of n){let n=Qe(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function Xe(e){try{let t=c(f(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),n=t.findIndex(e=>/^packages\\s*:/.test(e));if(n===-1)return null;let r=[];for(let e=n+1;e<t.length;e++){let n=t[e],i=/^\\s+-\\s+(.+)$/.exec(n);if(!i)break;r.push(i[1].trim().replace(/['\"`]/g,``))}return r.length>0?r:null}catch{return null}}const Ze=L({name:F()});function Qe(e){try{let t=c(f(e,`package.json`),`utf-8`),n=Ze.safeParse(JSON.parse(t));return n.success?n.data.name:null}catch{return null}}function $e(e){try{let t=c(f(e,`package.json`),`utf-8`),n=Ge.safeParse(JSON.parse(t));return n.success?n.data.scripts:null}catch{return null}}const et=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),tt=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function K(e){return et.has(e)?!0:tt.some(t=>e.startsWith(t))}function nt(e,t){let n=V(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return d.isAbsolute(i)?i:d.resolve(t.cwd,i)}function q(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=Ue(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=nt(e.command,o);t&&(o={...o,cwd:t})}let t=rt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function J(e,t){if(e.pre){let n=q(e.pre,t);if(n.action!==`allow`)return n}let n=q(e.main,t);if(n.action!==`allow`)return n;if(e.post){let n=q(e.post,t);if(n.action!==`allow`)return n}return`allow`}function rt(n,r){let i=n.trim();if(i.startsWith(`(`)&&i.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let a=V(n);if(!a.ok)return{action:`passthrough`,rule:null,reason:a.error};let o=a.tokens;if(o.length===0)return{action:`passthrough`,rule:null,reason:null};let s=0;for(;s<o.length&&/^\\w+=/.test(o[s]);){let e=o[s].split(`=`)[0];if(K(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};s++}if(s>=o.length)return{action:`allow`,rule:`env-only`,reason:null};o=o.slice(s);let c=We(o,n);if(!c.safe)return{action:`passthrough`,rule:null,reason:c.reason};for(let e=0;e<o.length;e++){let t=o[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(o[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let l=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);o=o.filter((e,t)=>!(l.has(e)||(e===`>`||e===`>>`)&&o[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(o[t-1]===`>`||o[t-1]===`>>`)));let u=d.basename(o[0]);if(u===`command`&&o.length>1)return q(o.slice(1).join(` `),{...r,depth:r.depth+1});if(u===`env`){let e=1;for(;e<o.length&&/^\\w+=/.test(o[e]);){let t=o[e].split(`=`)[0];if(K(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=o.length?{action:`allow`,rule:`env-only`,reason:null}:q(o.slice(e).join(` `),{...r,depth:r.depth+1})}if(u===`pnpm`)return it(o,r);if(u===`npm`)return ot(o);if(u===`bash`||u===`sh`||u===`zsh`)return lt(o,r);if(u===`xargs`)return dt(o,r);if(u===`node`&&(o.includes(`-e`)||o.includes(`-p`)||o.includes(`--eval`)||o.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(u===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let t of e)if(t.match.test(u)&&(!t.when||t.when(o,r)))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let e of t)if(e.match.test(u)&&(!e.when||e.when(o,r)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${u}`}}function it(e,n){let r=e.slice(1),a,o=-1,s=!1,c=!1,l;for(let t=0;t<r.length;t++){let u=r[t];if(u===`--filter`||u.startsWith(`--filter=`))return st(e,r,t,n);if(u===`-r`||u===`--recursive`){s=!0;continue}if(u===`-w`||u===`--workspace-root`){c=!0;continue}if(u===`-C`||u===`--dir`){l=r[++t];continue}if(u.startsWith(`-C=`)||u.startsWith(`--dir=`)){l=u.split(`=`,2)[1];continue}if(!(u.startsWith(`-`)&&!i.has(u))){a=u,o=t;break}}if(!a)return{action:`passthrough`,rule:null,reason:null};if(a===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(a===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(a===`exec`){let e=r.slice(o+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:q(e.join(` `),{...n,depth:n.depth+1})}let u;if(a===`run`){let e=r[o+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};u=e}else if(!i.has(a))u=a;else{for(let r of t)if(r.match.test(`pnpm`)&&(!r.when||r.when(e,n)))return{action:`allow`,rule:r.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(s)return at(u,n);if(c){let e=Je(n.cwd);return e?Y(u,{...n,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(l){let e=d.resolve(n.cwd,l);return Y(u,{...n,cwd:e})}return Y(u,n)}function Y(e,t){let n=W(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=J(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function at(e,t){let n=qe(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=J(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function ot(e){for(let n of t)if(n.match.test(`npm`)&&(!n.when||n.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:n.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:r(e)}}function st(e,t,n,r){let a=t[n],o,s;if(a.startsWith(`--filter=`))o=a.slice(9),s=n+1;else{if(o=t[n+1],!o||o.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};s=n+2}if(!o)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let c;for(let e=s;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!i.has(n))){c=n;break}}if(!c)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(c===`install`||c===`i`)return t.slice(t.indexOf(c)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(c===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(c===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(c))return{action:`allow`,rule:`pnpm`,reason:null};if(c===`exec`){let e=t.indexOf(`exec`,s),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:q(n.join(` `),{...r,depth:r.depth+1})}let l=c===`run`?t[t.indexOf(`run`,s)+1]:c;if(!l||l.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(i.has(l)&&l!==`run`)return{action:`passthrough`,rule:null,reason:null};let u=Ke(l,o,r.cwd);if(!u)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let d=J(u,{...r,depth:r.depth+1,cwd:u.pkgDir??r.cwd});return d===`allow`?{action:`allow`,rule:`pnpm-filter:${o}:${l}`,reason:null}:d}const ct=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function lt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||ct.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?q(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const X=new Set([`-I`,`-d`,`-n`,`-P`,`-s`,`-L`,`-E`,`-a`,`--max-args`,`--max-procs`,`--delimiter`,`--replace`,`--max-lines`,`--max-chars`,`--arg-file`,`--process-slot-var`]),ut=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function dt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(X.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(X.has(e)){n++;continue}}if(ut.has(t)){n++;continue}return{action:`passthrough`,rule:null,reason:`xargs with unrecognised flags`}}return n>=e.length?{action:`passthrough`,rule:null,reason:`xargs without command`}:q(e.slice(n).join(` `),{...t,depth:t.depth+1})}const ft=new Set([`Read`,`Write`,`Edit`]),pt=new Set([`Glob`,`Grep`]);function mt(e,t,n){return ft.has(e)?Le(t)?.file_path??null:pt.has(e)?Re(t)?.path??n:null}function ht(e,t,r){let i=mt(e,t,r.cwd);if(i===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of n)if(t.tools.has(e)&&t.when(i,r))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of a)if(t.tools.has(e)&&t.when(i,r))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await Me();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);let Q,$;if(Z.tool_name===`Bash`){let e=Ie(Z.tool_input);e||process.exit(0),Q=q(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=ht(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}He({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?B(`allow`):Q.action===`deny`&&B(`deny`,Q.reason);export{};", "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- **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- **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")];
|
|
2477
2671
|
}
|
|
2478
2672
|
//#endregion
|
|
2479
2673
|
//#region src/generators/claude-settings.ts
|
|
@@ -2742,175 +2936,6 @@ async function generateChangesets(ctx) {
|
|
|
2742
2936
|
};
|
|
2743
2937
|
}
|
|
2744
2938
|
//#endregion
|
|
2745
|
-
//#region src/generators/release-ci.ts
|
|
2746
|
-
/** Common setup steps shared by release-it and simple strategies. */
|
|
2747
|
-
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
2748
|
-
const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
|
|
2749
|
-
return [
|
|
2750
|
-
{ step: {
|
|
2751
|
-
uses: "actions/checkout@v6",
|
|
2752
|
-
with: { "fetch-depth": 0 }
|
|
2753
|
-
} },
|
|
2754
|
-
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
2755
|
-
{ step: {
|
|
2756
|
-
uses: "actions/setup-node@v6",
|
|
2757
|
-
with: {
|
|
2758
|
-
...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2759
|
-
cache: "pnpm",
|
|
2760
|
-
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
2761
|
-
}
|
|
2762
|
-
} },
|
|
2763
|
-
{ step: { run: "pnpm install --frozen-lockfile" } }
|
|
2764
|
-
];
|
|
2765
|
-
}
|
|
2766
|
-
function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
|
|
2767
|
-
const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
|
|
2768
|
-
const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
|
|
2769
|
-
return [...commonSteps(nodeVersionYaml, publishesNpm), { step: {
|
|
2770
|
-
run: "pnpm release-it --ci",
|
|
2771
|
-
env: {
|
|
2772
|
-
...tokenEnv,
|
|
2773
|
-
...npmEnv,
|
|
2774
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2775
|
-
}
|
|
2776
|
-
} }];
|
|
2777
|
-
}
|
|
2778
|
-
/** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
|
|
2779
|
-
function simpleWorkflowDispatchTrigger() {
|
|
2780
|
-
return { workflow_dispatch: { inputs: {
|
|
2781
|
-
bump: {
|
|
2782
|
-
description: "Version bump type (default: conventional-commits auto-detect)",
|
|
2783
|
-
required: false,
|
|
2784
|
-
type: "choice",
|
|
2785
|
-
default: "auto",
|
|
2786
|
-
options: [
|
|
2787
|
-
"auto",
|
|
2788
|
-
"major",
|
|
2789
|
-
"minor",
|
|
2790
|
-
"patch",
|
|
2791
|
-
"first-release"
|
|
2792
|
-
]
|
|
2793
|
-
},
|
|
2794
|
-
prerelease: {
|
|
2795
|
-
description: "Create a prerelease with the given tag (e.g., beta, alpha)",
|
|
2796
|
-
required: false,
|
|
2797
|
-
type: "string"
|
|
2798
|
-
}
|
|
2799
|
-
} } };
|
|
2800
|
-
}
|
|
2801
|
-
/** Build the release:simple run command with conditional flags from workflow inputs. */
|
|
2802
|
-
function simpleReleaseCommand() {
|
|
2803
|
-
return [
|
|
2804
|
-
"FLAGS=",
|
|
2805
|
-
`case "${actionsExpr("inputs.bump")}" in`,
|
|
2806
|
-
" major|minor|patch) FLAGS=\"$FLAGS --release-as " + actionsExpr("inputs.bump") + "\" ;;",
|
|
2807
|
-
" first-release) FLAGS=\"$FLAGS --first-release\" ;;",
|
|
2808
|
-
"esac",
|
|
2809
|
-
`if [ -n "${actionsExpr("inputs.prerelease")}" ]; then FLAGS="$FLAGS --prerelease ${actionsExpr("inputs.prerelease")}"; fi`,
|
|
2810
|
-
"pnpm exec bst release:simple $FLAGS"
|
|
2811
|
-
].join("\n");
|
|
2812
|
-
}
|
|
2813
|
-
/** Build the docker publish env block, omitting registry vars when derivable at runtime. */
|
|
2814
|
-
function dockerPublishEnv(registryDerivable) {
|
|
2815
|
-
return {
|
|
2816
|
-
...registryDerivable ? {} : {
|
|
2817
|
-
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
2818
|
-
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")
|
|
2819
|
-
},
|
|
2820
|
-
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
2821
|
-
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
2822
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2823
|
-
};
|
|
2824
|
-
}
|
|
2825
|
-
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2826
|
-
const releaseStep = { step: {
|
|
2827
|
-
name: "Release",
|
|
2828
|
-
env: {
|
|
2829
|
-
...ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
2830
|
-
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2831
|
-
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2832
|
-
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2833
|
-
},
|
|
2834
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2835
|
-
},
|
|
2836
|
-
run: simpleReleaseCommand()
|
|
2837
|
-
} };
|
|
2838
|
-
const dockerStep = { step: {
|
|
2839
|
-
name: "Publish Docker images",
|
|
2840
|
-
if: "success()",
|
|
2841
|
-
env: dockerPublishEnv(registryDerivable),
|
|
2842
|
-
run: "pnpm exec bst docker:publish"
|
|
2843
|
-
} };
|
|
2844
|
-
return [
|
|
2845
|
-
...commonSteps(nodeVersionYaml, publishesNpm),
|
|
2846
|
-
releaseStep,
|
|
2847
|
-
...hasDocker ? [dockerStep] : []
|
|
2848
|
-
];
|
|
2849
|
-
}
|
|
2850
|
-
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2851
|
-
switch (strategy) {
|
|
2852
|
-
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
2853
|
-
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2854
|
-
default: return null;
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
async function generateReleaseCi(ctx) {
|
|
2858
|
-
const filePath = "release-ci";
|
|
2859
|
-
if (ctx.config.releaseStrategy === "none" || ctx.config.ci === "none") return {
|
|
2860
|
-
filePath,
|
|
2861
|
-
action: "skipped",
|
|
2862
|
-
description: "Release CI workflow not applicable"
|
|
2863
|
-
};
|
|
2864
|
-
const publishesNpm = ctx.config.publishNpm === true;
|
|
2865
|
-
if (ctx.config.releaseStrategy === "changesets") return {
|
|
2866
|
-
filePath,
|
|
2867
|
-
action: "skipped",
|
|
2868
|
-
description: "Release step included in CI workflow"
|
|
2869
|
-
};
|
|
2870
|
-
const isGitHub = ctx.config.ci === "github";
|
|
2871
|
-
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
2872
|
-
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2873
|
-
const hasDocker = hasDockerPackages(ctx);
|
|
2874
|
-
const registryDerivable = hasRepositoryUrl(ctx);
|
|
2875
|
-
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2876
|
-
if (!steps) return {
|
|
2877
|
-
filePath,
|
|
2878
|
-
action: "skipped",
|
|
2879
|
-
description: "Release CI workflow not applicable"
|
|
2880
|
-
};
|
|
2881
|
-
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
2882
|
-
const content = buildWorkflowYaml({
|
|
2883
|
-
ci: ctx.config.ci,
|
|
2884
|
-
name: "Release",
|
|
2885
|
-
on,
|
|
2886
|
-
...isGitHub && { permissions: { contents: "write" } },
|
|
2887
|
-
jobName: "release",
|
|
2888
|
-
steps
|
|
2889
|
-
});
|
|
2890
|
-
const alreadyExists = ctx.exists(workflowPath);
|
|
2891
|
-
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
2892
|
-
if (existing) {
|
|
2893
|
-
if (isToolingIgnored(existing)) return {
|
|
2894
|
-
filePath: workflowPath,
|
|
2895
|
-
action: "skipped",
|
|
2896
|
-
description: "Release workflow has ignore comment"
|
|
2897
|
-
};
|
|
2898
|
-
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
2899
|
-
filePath: workflowPath,
|
|
2900
|
-
action: "skipped",
|
|
2901
|
-
description: "Release workflow already up to date"
|
|
2902
|
-
};
|
|
2903
|
-
}
|
|
2904
|
-
const withDevPath = preserveDevBinaryPath(content, existing);
|
|
2905
|
-
const final = existing ? insertCustomBlocks(withDevPath, extractCustomBlocks(existing)) : withDevPath;
|
|
2906
|
-
ctx.write(workflowPath, final);
|
|
2907
|
-
return {
|
|
2908
|
-
filePath: workflowPath,
|
|
2909
|
-
action: alreadyExists ? "updated" : "created",
|
|
2910
|
-
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
//#endregion
|
|
2914
2939
|
//#region src/generators/lefthook.ts
|
|
2915
2940
|
function requiredCommands(formatter) {
|
|
2916
2941
|
return {
|
|
@@ -3268,7 +3293,7 @@ async function runGenerators(ctx) {
|
|
|
3268
3293
|
function generateMigratePrompt(results, config, detected) {
|
|
3269
3294
|
const sections = [];
|
|
3270
3295
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3271
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3296
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.42.0 repo:sync\` on ${timestamp}_\n`);
|
|
3272
3297
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3273
3298
|
if (changed.length > 0) {
|
|
3274
3299
|
const items = changed.map((r) => {
|
|
@@ -5506,37 +5531,36 @@ const TOKEN_SCOPES = [
|
|
|
5506
5531
|
"write:package",
|
|
5507
5532
|
"write:repository"
|
|
5508
5533
|
];
|
|
5534
|
+
function createForgejoSecretsProvider(executor, conn) {
|
|
5535
|
+
return { async setSecret(_repo, name, value) {
|
|
5536
|
+
await setActionSecret(executor, conn, name, value);
|
|
5537
|
+
} };
|
|
5538
|
+
}
|
|
5539
|
+
function createGitHubSecretsProvider(executor) {
|
|
5540
|
+
return { async setSecret(repo, name, value) {
|
|
5541
|
+
const result = executor.exec("gh", [
|
|
5542
|
+
"secret",
|
|
5543
|
+
"set",
|
|
5544
|
+
name,
|
|
5545
|
+
"--body",
|
|
5546
|
+
value,
|
|
5547
|
+
"-R",
|
|
5548
|
+
repo
|
|
5549
|
+
]);
|
|
5550
|
+
if (result.exitCode !== 0) throw new FatalError(`Failed to set GitHub secret ${name}: ${result.stderr}`);
|
|
5551
|
+
} };
|
|
5552
|
+
}
|
|
5509
5553
|
/**
|
|
5510
5554
|
* Core logic for setup:secrets, separated from the command handler for testability.
|
|
5555
|
+
* Platform-agnostic — uses the injected SecretsProvider to set secrets.
|
|
5511
5556
|
*/
|
|
5512
|
-
async function runSetupSecrets(executor, config) {
|
|
5557
|
+
async function runSetupSecrets(executor, provider, config) {
|
|
5513
5558
|
const repoName = config.repository.split("/")[1] ?? config.repository;
|
|
5514
|
-
const tokenName = `bst-release-${repoName}`;
|
|
5515
5559
|
const secretsSet = [];
|
|
5516
|
-
let token;
|
|
5517
|
-
if (config.dryRun) {
|
|
5518
|
-
log.info(`[dry-run] Would create access token "${tokenName}" for ${config.username}`);
|
|
5519
|
-
log.info(`[dry-run] Scopes: ${TOKEN_SCOPES.join(", ")}`);
|
|
5520
|
-
token = "dry-run-token";
|
|
5521
|
-
} else {
|
|
5522
|
-
log.info(`Creating access token "${tokenName}"...`);
|
|
5523
|
-
try {
|
|
5524
|
-
token = await createAccessToken(executor, config.serverUrl, config.username, config.password, tokenName, TOKEN_SCOPES);
|
|
5525
|
-
log.success(`Created access token "${tokenName}"`);
|
|
5526
|
-
} catch (error) {
|
|
5527
|
-
if (error instanceof Error && error.message.includes("422")) throw new FatalError(`Access token "${tokenName}" already exists. Delete it in Forgejo settings and re-run, or set secrets manually.`);
|
|
5528
|
-
throw error;
|
|
5529
|
-
}
|
|
5530
|
-
}
|
|
5531
|
-
const conn = {
|
|
5532
|
-
serverUrl: config.serverUrl,
|
|
5533
|
-
repository: config.repository,
|
|
5534
|
-
token
|
|
5535
|
-
};
|
|
5536
5560
|
if (config.dryRun) log.info(`[dry-run] Would set secret RELEASE_TOKEN on ${config.repository}`);
|
|
5537
5561
|
else {
|
|
5538
5562
|
log.info("Setting RELEASE_TOKEN secret...");
|
|
5539
|
-
await
|
|
5563
|
+
await provider.setSecret(config.repository, "RELEASE_TOKEN", config.releaseToken);
|
|
5540
5564
|
log.success("Set RELEASE_TOKEN");
|
|
5541
5565
|
}
|
|
5542
5566
|
secretsSet.push("RELEASE_TOKEN");
|
|
@@ -5546,27 +5570,105 @@ async function runSetupSecrets(executor, config) {
|
|
|
5546
5570
|
const dockerPackages = detectDockerPackages(executor, config.cwd, rootName);
|
|
5547
5571
|
if (dockerPackages.length > 0) {
|
|
5548
5572
|
log.info(`Detected ${String(dockerPackages.length)} Docker package(s) — setting Docker secrets`);
|
|
5573
|
+
const dockerUser = config.dockerCredentials?.username ?? repoName;
|
|
5574
|
+
const dockerPass = config.dockerCredentials?.password ?? config.releaseToken;
|
|
5575
|
+
if (!config.dockerCredentials) log.info(`Using defaults for Docker secrets: username="${dockerUser}", password=<release token>`);
|
|
5549
5576
|
if (config.dryRun) {
|
|
5550
5577
|
log.info(`[dry-run] Would set secret DOCKER_USERNAME on ${config.repository}`);
|
|
5551
5578
|
log.info(`[dry-run] Would set secret DOCKER_PASSWORD on ${config.repository}`);
|
|
5552
5579
|
} else {
|
|
5553
|
-
await
|
|
5580
|
+
await provider.setSecret(config.repository, "DOCKER_USERNAME", dockerUser);
|
|
5554
5581
|
log.success("Set DOCKER_USERNAME");
|
|
5555
|
-
await
|
|
5582
|
+
await provider.setSecret(config.repository, "DOCKER_PASSWORD", dockerPass);
|
|
5556
5583
|
log.success("Set DOCKER_PASSWORD");
|
|
5557
5584
|
}
|
|
5558
5585
|
secretsSet.push("DOCKER_USERNAME", "DOCKER_PASSWORD");
|
|
5559
5586
|
}
|
|
5560
5587
|
}
|
|
5588
|
+
return { secretsSet };
|
|
5589
|
+
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Create a Forgejo access token with user-friendly error wrapping.
|
|
5592
|
+
* Exported for testability — called by the command handler's Forgejo path.
|
|
5593
|
+
*/
|
|
5594
|
+
async function createForgejoReleaseToken(executor, config) {
|
|
5595
|
+
const tokenName = `bst-release-${config.repository.split("/")[1] ?? config.repository}`;
|
|
5596
|
+
if (config.dryRun) {
|
|
5597
|
+
log.info(`[dry-run] Would create access token "${tokenName}" for ${config.username}`);
|
|
5598
|
+
log.info(`[dry-run] Scopes: ${TOKEN_SCOPES.join(", ")}`);
|
|
5599
|
+
return {
|
|
5600
|
+
token: "dry-run-token",
|
|
5601
|
+
tokenName
|
|
5602
|
+
};
|
|
5603
|
+
}
|
|
5604
|
+
log.info(`Creating access token "${tokenName}"...`);
|
|
5605
|
+
try {
|
|
5606
|
+
const token = await createAccessToken(executor, config.serverUrl, config.username, config.password, tokenName, TOKEN_SCOPES);
|
|
5607
|
+
log.success(`Created access token "${tokenName}"`);
|
|
5608
|
+
return {
|
|
5609
|
+
token,
|
|
5610
|
+
tokenName
|
|
5611
|
+
};
|
|
5612
|
+
} catch (error) {
|
|
5613
|
+
if (error instanceof Error && error.message.includes("422")) throw new FatalError(`Access token "${tokenName}" already exists. Delete it in Forgejo settings and re-run, or set secrets manually.`);
|
|
5614
|
+
throw error;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
async function setupForgejo(executor, serverUrl, repository, dryRun) {
|
|
5618
|
+
const username = await p.text({
|
|
5619
|
+
message: "Forgejo username",
|
|
5620
|
+
validate: (v) => !v || v.length === 0 ? "Username is required" : void 0
|
|
5621
|
+
});
|
|
5622
|
+
if (isCancelled(username)) {
|
|
5623
|
+
p.cancel("Cancelled.");
|
|
5624
|
+
process.exit(0);
|
|
5625
|
+
}
|
|
5626
|
+
const password = await p.password({
|
|
5627
|
+
message: "Forgejo password",
|
|
5628
|
+
validate: (v) => !v || v.length === 0 ? "Password is required" : void 0
|
|
5629
|
+
});
|
|
5630
|
+
if (isCancelled(password)) {
|
|
5631
|
+
p.cancel("Cancelled.");
|
|
5632
|
+
process.exit(0);
|
|
5633
|
+
}
|
|
5634
|
+
const { token } = await createForgejoReleaseToken(executor, {
|
|
5635
|
+
serverUrl,
|
|
5636
|
+
repository,
|
|
5637
|
+
username,
|
|
5638
|
+
password,
|
|
5639
|
+
dryRun
|
|
5640
|
+
});
|
|
5641
|
+
return {
|
|
5642
|
+
provider: createForgejoSecretsProvider(executor, {
|
|
5643
|
+
serverUrl,
|
|
5644
|
+
repository,
|
|
5645
|
+
token
|
|
5646
|
+
}),
|
|
5647
|
+
releaseToken: token,
|
|
5648
|
+
dockerCredentials: {
|
|
5649
|
+
username,
|
|
5650
|
+
password: token
|
|
5651
|
+
}
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5654
|
+
async function setupGitHub(executor) {
|
|
5655
|
+
const token = await p.password({
|
|
5656
|
+
message: "GitHub Personal Access Token (PAT)",
|
|
5657
|
+
validate: (v) => !v || v.length === 0 ? "Token is required" : void 0
|
|
5658
|
+
});
|
|
5659
|
+
if (isCancelled(token)) {
|
|
5660
|
+
p.cancel("Cancelled.");
|
|
5661
|
+
process.exit(0);
|
|
5662
|
+
}
|
|
5561
5663
|
return {
|
|
5562
|
-
|
|
5563
|
-
|
|
5664
|
+
provider: createGitHubSecretsProvider(executor),
|
|
5665
|
+
releaseToken: token
|
|
5564
5666
|
};
|
|
5565
5667
|
}
|
|
5566
5668
|
const setupSecretsCommand = defineCommand({
|
|
5567
5669
|
meta: {
|
|
5568
5670
|
name: "setup:secrets",
|
|
5569
|
-
description: "
|
|
5671
|
+
description: "Configure CI secrets for this repository (Forgejo or GitHub)"
|
|
5570
5672
|
},
|
|
5571
5673
|
args: {
|
|
5572
5674
|
"dry-run": {
|
|
@@ -5576,49 +5678,41 @@ const setupSecretsCommand = defineCommand({
|
|
|
5576
5678
|
"no-docker": {
|
|
5577
5679
|
type: "boolean",
|
|
5578
5680
|
description: "Skip Docker secrets setup even if Docker packages are detected"
|
|
5579
|
-
},
|
|
5580
|
-
verbose: {
|
|
5581
|
-
type: "boolean",
|
|
5582
|
-
description: "Enable verbose output"
|
|
5583
5681
|
}
|
|
5584
5682
|
},
|
|
5585
5683
|
async run({ args }) {
|
|
5586
5684
|
const cwd = process.cwd();
|
|
5587
|
-
const verbose = args.verbose === true || isEnvVerbose();
|
|
5588
5685
|
const dryRun = args["dry-run"] === true;
|
|
5589
5686
|
const noDocker = args["no-docker"] === true;
|
|
5590
5687
|
p.intro("setup:secrets");
|
|
5591
5688
|
const parsed = parseRepositoryUrl(cwd);
|
|
5592
|
-
if (!parsed) throw new FatalError("No repository URL found in package.json. Add a `repository` field
|
|
5593
|
-
if (parsed.hostname === "github.com") throw new FatalError("setup:secrets is for Forgejo repositories. GitHub uses different auth.");
|
|
5594
|
-
const serverUrl = `${parsed.protocol}//${parsed.hostname}`;
|
|
5689
|
+
if (!parsed) throw new FatalError("No repository URL found in package.json. Add a `repository` field.");
|
|
5595
5690
|
const repository = parsed.repository;
|
|
5596
|
-
|
|
5597
|
-
const
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
if (
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5691
|
+
const isGitHub = parsed.hostname === "github.com";
|
|
5692
|
+
const executor = createRealExecutor();
|
|
5693
|
+
let provider;
|
|
5694
|
+
let releaseToken;
|
|
5695
|
+
let dockerCredentials;
|
|
5696
|
+
if (isGitHub) {
|
|
5697
|
+
log.info(`GitHub: ${repository}`);
|
|
5698
|
+
const setup = await setupGitHub(executor);
|
|
5699
|
+
provider = setup.provider;
|
|
5700
|
+
releaseToken = setup.releaseToken;
|
|
5701
|
+
} else {
|
|
5702
|
+
const serverUrl = `${parsed.protocol}//${parsed.hostname}`;
|
|
5703
|
+
log.info(`Forgejo: ${serverUrl} / ${repository}`);
|
|
5704
|
+
const setup = await setupForgejo(executor, serverUrl, repository, dryRun);
|
|
5705
|
+
provider = setup.provider;
|
|
5706
|
+
releaseToken = setup.releaseToken;
|
|
5707
|
+
dockerCredentials = setup.dockerCredentials;
|
|
5612
5708
|
}
|
|
5613
|
-
const summary = (await runSetupSecrets(
|
|
5614
|
-
serverUrl,
|
|
5709
|
+
const summary = (await runSetupSecrets(executor, provider, {
|
|
5615
5710
|
repository,
|
|
5616
|
-
|
|
5617
|
-
password,
|
|
5711
|
+
releaseToken,
|
|
5618
5712
|
cwd,
|
|
5619
5713
|
dryRun,
|
|
5620
5714
|
noDocker,
|
|
5621
|
-
|
|
5715
|
+
dockerCredentials
|
|
5622
5716
|
})).secretsSet.map((s) => ` - ${s}`).join("\n");
|
|
5623
5717
|
p.outro(`Done! Secrets ${dryRun ? "that would be" : ""} configured:\n${summary}`);
|
|
5624
5718
|
}
|
|
@@ -5756,7 +5850,7 @@ const forgejoSecretsCommand = defineCommand({
|
|
|
5756
5850
|
const main = defineCommand({
|
|
5757
5851
|
meta: {
|
|
5758
5852
|
name: "bst",
|
|
5759
|
-
version: "0.
|
|
5853
|
+
version: "0.42.0",
|
|
5760
5854
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
5761
5855
|
},
|
|
5762
5856
|
subCommands: {
|
|
@@ -5775,7 +5869,7 @@ const main = defineCommand({
|
|
|
5775
5869
|
"forgejo:secrets": forgejoSecretsCommand
|
|
5776
5870
|
}
|
|
5777
5871
|
});
|
|
5778
|
-
console.log(`@bensandee/tooling v0.
|
|
5872
|
+
console.log(`@bensandee/tooling v0.42.0`);
|
|
5779
5873
|
async function run() {
|
|
5780
5874
|
await runMain(main);
|
|
5781
5875
|
process.exit(process.exitCode ?? 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"citty": "^0.2.1",
|
|
41
41
|
"json5": "^2.2.3",
|
|
42
42
|
"jsonc-parser": "^3.3.1",
|
|
43
|
-
"picomatch": "^4.0.
|
|
44
|
-
"yaml": "^2.8.
|
|
43
|
+
"picomatch": "^4.0.4",
|
|
44
|
+
"yaml": "^2.8.3",
|
|
45
45
|
"zod": "^4.3.6",
|
|
46
46
|
"@bensandee/common": "0.1.2"
|
|
47
47
|
},
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"commit-and-tag-version": "12.7.1",
|
|
54
54
|
"prettier": "3.8.1",
|
|
55
55
|
"release-it": "19.2.4",
|
|
56
|
-
"tsdown": "0.21.
|
|
57
|
-
"@bensandee/config": "0.10.
|
|
58
|
-
"@bensandee/claude-hooks": "0.
|
|
56
|
+
"tsdown": "0.21.6",
|
|
57
|
+
"@bensandee/config": "0.10.1",
|
|
58
|
+
"@bensandee/claude-hooks": "0.6.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|
package/tooling.schema.json
CHANGED
|
@@ -168,6 +168,10 @@
|
|
|
168
168
|
"registryNamespace": {
|
|
169
169
|
"description": "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)",
|
|
170
170
|
"type": "string"
|
|
171
|
+
},
|
|
172
|
+
"postReleaseHook": {
|
|
173
|
+
"description": "Override the post-release hook command (default: curl POST_RELEASE_HOOK_URL secret if set)",
|
|
174
|
+
"type": "string"
|
|
171
175
|
}
|
|
172
176
|
},
|
|
173
177
|
"additionalProperties": false
|