@bensandee/tooling 0.89.1 → 0.91.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 +1 -1
- package/dist/bin.mjs +1106 -568
- package/package.json +4 -4
- package/tooling.schema.json +9 -4
package/dist/bin.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { accessSync, appendFileSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
3
|
-
import * as path$1 from "node:path";
|
|
4
|
-
import path from "node:path";
|
|
3
|
+
import path, * as path$1 from "node:path";
|
|
5
4
|
import { parseArgs, parseEnv } from "node:util";
|
|
6
5
|
import { FatalError, TransientError, UnexpectedError, describeError, describeError as describeError$1, redact, redact as redact$1 } from "@bensandee/common";
|
|
7
6
|
import { defineCommand, renderUsage, runCommand, runMain } from "citty";
|
|
@@ -176,7 +175,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
|
|
|
176
175
|
//#region src/env-file-preload.ts
|
|
177
176
|
const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
|
|
178
177
|
name: "bst",
|
|
179
|
-
version: "0.
|
|
178
|
+
version: "0.91.0"
|
|
180
179
|
});
|
|
181
180
|
//#endregion
|
|
182
181
|
//#region ../cli-kit/src/logger.ts
|
|
@@ -784,7 +783,7 @@ const LintConfigSchema = z.strictObject({ strictTypeAware: z.boolean().optional(
|
|
|
784
783
|
const ChecksConfigSchema = z.strictObject({
|
|
785
784
|
add: z.array(z.string()).optional().meta({ description: "Extra package.json scripts to run as first-class check steps, appended to the built-in list (e.g. ['lint:php']). An added step with no matching script is a hard failure." }),
|
|
786
785
|
skip: z.array(z.string()).optional().meta({ description: "Check steps to skip, unioned with the built-in CI defaults (never replacing them). Supports glob patterns." }),
|
|
787
|
-
runOnVersionPr: z.boolean().optional().meta({ description: "Run the CI check job on the changesets version PR (changeset-release
|
|
786
|
+
runOnVersionPr: z.boolean().optional().meta({ description: "Run the CI check job on the changesets version PR (changeset-release/<defaultBranch>). Default false: the generated check job is skipped there, because that branch is regenerated on every push to the default branch and that branch's own run gates the release. Has no effect unless releaseStrategy is changesets." })
|
|
788
787
|
});
|
|
789
788
|
const CoverageConfigSchema = z.strictObject({
|
|
790
789
|
history: z.enum(["orphan-branch", "none"]).optional().meta({ description: "How coverage history is persisted. 'orphan-branch' (default) appends to a dedicated branch; 'none' disables CI history but keeps the local ratchet." }),
|
|
@@ -828,7 +827,7 @@ const DockerImageOverrideSchema = z.strictObject({
|
|
|
828
827
|
buildArgs: z.record(z.string(), z.string()).optional().meta({ description: "Build args passed to `docker build` for this package. Values support ${VAR} and ${VAR:-default} expansion against process.env." }),
|
|
829
828
|
imageName: z.string().min(1).optional().meta({ description: "Image name used for docker:build tags, docker:publish refs, and docker:check cross-referencing. Falls back to the derived name: the package.json name for the root package (npm scope stripped), or {root-name}-{package-name} for a monorepo package. A value that already carries a registry host (e.g. 'ghcr.io/me/app') is used as written by docker:publish, with no registryNamespace prefix added, and must name the registry this run logs in to -- one naming any other host refuses before login." }),
|
|
830
829
|
version: z.string().optional().meta({ description: "Version used for docker:publish tags; overrides the version resolved from the enclosing package." }),
|
|
831
|
-
publish: z.boolean().optional().meta({ description: "The publish decision either way: true keeps an image published -- including one
|
|
830
|
+
publish: z.boolean().optional().meta({ description: "The publish decision either way: true keeps an image published -- including one whose compose service is declared only in the check overlay, which is not published by default -- and false excludes it while publishDocker stays true. Unset means the discovery default, which publishes everything except a check-only image." })
|
|
832
831
|
});
|
|
833
832
|
const DockerMapSchema = z.record(z.string(), DockerImageOverrideSchema);
|
|
834
833
|
const TolerantDockerMapSchema = z.record(z.string(), tolerantBlock(DockerImageOverrideSchema));
|
|
@@ -836,6 +835,16 @@ const TolerantDockerCheckConfigSchema = z.object({
|
|
|
836
835
|
...DockerCheckConfigSchema.shape,
|
|
837
836
|
healthChecks: z.array(tolerantBlock(DeclarativeHealthCheckSchema)).optional()
|
|
838
837
|
});
|
|
838
|
+
const DEFAULT_BRANCH_NAME_PATTERN = /^(?!.*\/\/)(?!.*\.\.)(?!.*\/$)(?!.*\.lock$)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
839
|
+
const DefaultBranchNameSchema = z.string().regex(DEFAULT_BRANCH_NAME_PATTERN, "must be a plain branch name: ASCII letters, digits, '.', '_', '-' and '/', starting with a letter or digit, with no '//', '..', trailing '/', or '.lock' suffix");
|
|
840
|
+
const AgentEnabledSliceSchema = z.looseObject({ agent: z.looseObject({ enabled: z.boolean().optional() }).optional() });
|
|
841
|
+
function agentEnabledIn(raw) {
|
|
842
|
+
try {
|
|
843
|
+
return parseToolingConfigSlice(raw, AgentEnabledSliceSchema, { block: "agent" })?.agent?.enabled === true;
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
839
848
|
//#endregion
|
|
840
849
|
//#region src/utils/stack.ts
|
|
841
850
|
const IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1701,6 +1710,7 @@ const LintSweepConfigSchema = z.strictObject({
|
|
|
1701
1710
|
});
|
|
1702
1711
|
const AgentEgressConfigSchema = z.strictObject({ browseHosts: z.array(z.string().min(1)).optional().meta({ description: "Host patterns this repo's agent containers may fetch from, on top of the hosts every run needs: an exact host (`fastdl.mongodb.org`) or a wildcard over its leftmost label (`*.example.com`). No ports. These join the browse lane, which serves GET and HEAD only." }) });
|
|
1703
1712
|
const AgentConfigSchema = z.strictObject({
|
|
1713
|
+
enabled: z.boolean().optional().meta({ description: "The repo's explicit consent to the agent (default false). Only `true`, read from the repo's **default branch**, lets the agent work the repo: claim units, run sweeps, recover orphans, merge, act on a chat proposal or start a new spec. A missing key, `false`, a missing `.tooling.json` or a read that fails all mean not enabled, and the answer is re-read within about a minute. Every other `agent.*` key below has no effect without this one; `repo:sync` warns when the block sets one of them and this key is not `true`. Where this is `true`, `repo:sync` also installs `@bensandee/agent-cli`. `bst setup:agent` sets it to true and runs the sync. Provisioning the agent's labels is not consent, and is never gated by this key." }),
|
|
1704
1714
|
autonomy: z.boolean().optional().meta({ description: "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the agent applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the agent files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline. It also closes that issue, with a comment, if the default branch recovers on its own before a human acts on it. Also arms the world-changed retrigger sweep: a pull request labelled agent-flags/autopilot that is parked at a -stuck or -failed terminal is re-queued when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base)." }),
|
|
1705
1715
|
preRunRebase: z.boolean().optional().meta({ description: "Merge the current default branch into the work branch before the implement (execute) phase's container starts (default true). A clean merge or an already-up-to-date branch runs the container as is; a conflicting merge is aborted, recorded on the run's status comment, and the run proceeds on the un-synced branch -- it never fails the run. Two consecutive conflicting attempts hand the unit to agent/repair before any container runs; a timeout or a failed merge never counts toward that pair. Set false to skip the attempt entirely for this repo: no fetch, no merge, no abort, and the branch is worked exactly as cloned." }),
|
|
1706
1716
|
backlog: z.boolean().optional().meta({ description: "Whether the agent may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human. A run starts only once at least three findings are pending or one is older than seven days." }),
|
|
@@ -1769,7 +1779,7 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
1769
1779
|
docs: z.union([z.literal(false), DocsConfigSchema]).optional().meta({ description: "Plan/spec lifecycle gate (`bst docs:check`). Overrides only -- dirs are discovered by convention. Set to false to disable. Violations are advisory until a pair is named in `enforced`." }),
|
|
1770
1780
|
deploy: DeployConfigSchema.optional().meta({ description: "Generate a deploy-dispatch step in the release workflow that triggers a workflow_dispatch event in another repo after a successful release. The resolved release token must have rights on `dispatchRepo`." }),
|
|
1771
1781
|
pnpmWorkspace: z.literal(false).optional().meta({ description: "Set to false to opt out of pnpm-workspace.yaml management (skips both generation and the repo:sync --check validation)." }),
|
|
1772
|
-
defaultBranch:
|
|
1782
|
+
defaultBranch: DefaultBranchNameSchema.optional().meta({ description: "Repository default branch name, B. Drives the generated CI `on.push` trigger and every default-branch gate in `ci.yml` and `release.yml`, the release path (`release:changesets`, the version PR, `release:trigger`) and `checks:run`'s coverage gate. Generated output uses 'main' when unset; `checks:run` still falls back to `origin/HEAD`. Must be a plain branch name: ASCII letters, digits, '.', '_', '-' and '/'." }),
|
|
1773
1783
|
unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional().meta({ description: "devDependency names repo:sync must never add or re-pin. Escape hatch for holding a tooling-managed dep (oxlint, vitest, knip, ...) at a deliberately off-canonical version; without it, repo:sync raises any managed devDep whose version has drifted below the canonical pin." }),
|
|
1774
1784
|
stack: StackSchema.optional().meta({ description: "Whether the repo has TS/JS source. Detected from source evidence; TypeScript is never inferred from absence." }),
|
|
1775
1785
|
builder: z.enum([
|
|
@@ -1786,6 +1796,7 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
1786
1796
|
});
|
|
1787
1797
|
const TolerantToolingConfigSchema = z.object({
|
|
1788
1798
|
...ToolingConfigSchema.shape,
|
|
1799
|
+
defaultBranch: z.string().optional(),
|
|
1789
1800
|
docker: TolerantDockerMapSchema.optional(),
|
|
1790
1801
|
dockerCheck: z.union([z.literal(false), TolerantDockerCheckConfigSchema]).optional(),
|
|
1791
1802
|
coverage: z.union([z.literal(false), tolerantBlock(CoverageConfigSchema)]).optional(),
|
|
@@ -1831,10 +1842,16 @@ function loadToolingConfig(targetDir) {
|
|
|
1831
1842
|
if (!file) return void 0;
|
|
1832
1843
|
return parseConfigFile(file, TolerantToolingConfigSchema);
|
|
1833
1844
|
}
|
|
1845
|
+
function parseToolingConfigStrict(raw, displayPath) {
|
|
1846
|
+
return parseConfigFile({
|
|
1847
|
+
raw,
|
|
1848
|
+
displayPath
|
|
1849
|
+
}, ToolingConfigSchema);
|
|
1850
|
+
}
|
|
1834
1851
|
function loadToolingConfigStrict(targetDir) {
|
|
1835
1852
|
const file = readConfigFile(targetDir);
|
|
1836
1853
|
if (!file) return void 0;
|
|
1837
|
-
return
|
|
1854
|
+
return parseToolingConfigStrict(file.raw, file.displayPath);
|
|
1838
1855
|
}
|
|
1839
1856
|
const OVERRIDE_KEYS = [
|
|
1840
1857
|
"structure",
|
|
@@ -3375,7 +3392,7 @@ function localTarget(call, entry) {
|
|
|
3375
3392
|
}
|
|
3376
3393
|
//#endregion
|
|
3377
3394
|
//#region src/release/docker-check-only-phase.ts
|
|
3378
|
-
const DOCKER_CHECK_ONLY_PHASE = "
|
|
3395
|
+
const DOCKER_CHECK_ONLY_PHASE = "enforce";
|
|
3379
3396
|
//#endregion
|
|
3380
3397
|
//#region src/release/docker.ts
|
|
3381
3398
|
function defaultPublish(checkOnlyIn, phase) {
|
|
@@ -3816,7 +3833,7 @@ function runDockerBuild(executor, config) {
|
|
|
3816
3833
|
log$1.info(`Built ${pkg.imageName}:latest`);
|
|
3817
3834
|
return { packages: [pkg] };
|
|
3818
3835
|
}
|
|
3819
|
-
const packages = detectDockerPackages(executor, config.cwd, repoName, config.checkOnlyPhase ?? "
|
|
3836
|
+
const packages = detectDockerPackages(executor, config.cwd, repoName, config.checkOnlyPhase ?? "enforce");
|
|
3820
3837
|
if (packages.length === 0) {
|
|
3821
3838
|
log$1.info("No packages with docker config found");
|
|
3822
3839
|
return { packages: [] };
|
|
@@ -3976,7 +3993,7 @@ function reportCheckOnlyImages(packages, phase) {
|
|
|
3976
3993
|
}
|
|
3977
3994
|
}
|
|
3978
3995
|
function runDockerPublish(executor, config) {
|
|
3979
|
-
const checkOnlyPhase = config.checkOnlyPhase ?? "
|
|
3996
|
+
const checkOnlyPhase = config.checkOnlyPhase ?? "enforce";
|
|
3980
3997
|
const { packages } = runDockerBuild(executor, {
|
|
3981
3998
|
cwd: config.cwd,
|
|
3982
3999
|
packageDir: void 0,
|
|
@@ -4439,8 +4456,8 @@ function buildWorkflowYaml(options) {
|
|
|
4439
4456
|
}
|
|
4440
4457
|
//#endregion
|
|
4441
4458
|
//#region src/generators/mise-pin.ts
|
|
4442
|
-
const MISE_VERSION = "2026.10.
|
|
4443
|
-
const MISE_LINUX_X64_SHA256 = "
|
|
4459
|
+
const MISE_VERSION = "2026.10.8";
|
|
4460
|
+
const MISE_LINUX_X64_SHA256 = "c72477ae6c3a8e7d817a322f43da70e4643e75ee5c21265f1c19c894273b7428";
|
|
4444
4461
|
const SETUP_MISE_ACTION_URL = "https://code.orangebikelabs.com/bensandee/common/actions/setup-mise";
|
|
4445
4462
|
const SETUP_MISE_ACTION_TAG = "setup-mise@latest";
|
|
4446
4463
|
const SETUP_MISE_ACTION_SHA = "ebe622ef0d24c7efec4a5f6093ddb18500900e24";
|
|
@@ -4449,13 +4466,22 @@ const SETUP_MISE_ACTION_SHA = "ebe622ef0d24c7efec4a5f6093ddb18500900e24";
|
|
|
4449
4466
|
function actionsExpr(expr) {
|
|
4450
4467
|
return `\${{ ${expr} }}`;
|
|
4451
4468
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4469
|
+
function branchNameLiteral(name) {
|
|
4470
|
+
if (DEFAULT_BRANCH_NAME_PATTERN.test(name)) return name;
|
|
4471
|
+
return name.replace(/'/g, "''");
|
|
4472
|
+
}
|
|
4473
|
+
function isDefaultBranch(name) {
|
|
4474
|
+
return `github.ref == 'refs/heads/${branchNameLiteral(name)}'`;
|
|
4475
|
+
}
|
|
4476
|
+
function isNotDefaultBranch(name) {
|
|
4477
|
+
return `github.ref != 'refs/heads/${branchNameLiteral(name)}'`;
|
|
4478
|
+
}
|
|
4479
|
+
function defaultBranchJobEnv(name) {
|
|
4480
|
+
return { IS_MAIN: actionsExpr(isDefaultBranch(name)) };
|
|
4481
|
+
}
|
|
4456
4482
|
const IS_MAIN_ENV_GUARD = "env.IS_MAIN == 'true'";
|
|
4457
|
-
function onlyOnDefaultBranch(condition) {
|
|
4458
|
-
return `${
|
|
4483
|
+
function onlyOnDefaultBranch(name, condition) {
|
|
4484
|
+
return `${isDefaultBranch(name)} && (${condition})`;
|
|
4459
4485
|
}
|
|
4460
4486
|
function npmrcAuthKey(registryUrl) {
|
|
4461
4487
|
return `${registryUrl.replace(/^https?:/i, "").replace(/\/+$/, "")}/`;
|
|
@@ -4858,6 +4884,38 @@ function diagnoseCheckScriptForwardingAt(targetDir) {
|
|
|
4858
4884
|
return diagnoseCheckScriptForwarding(scripts);
|
|
4859
4885
|
}
|
|
4860
4886
|
//#endregion
|
|
4887
|
+
//#region src/generators/agent-cli-dep.ts
|
|
4888
|
+
const AGENT_CLI_PACKAGE = "@bensandee/agent-cli";
|
|
4889
|
+
function agentEnabled(ctx) {
|
|
4890
|
+
return agentEnabledIn(ctx.read(".tooling.json"));
|
|
4891
|
+
}
|
|
4892
|
+
function wantsAgentCli(ctx) {
|
|
4893
|
+
if (!agentEnabled(ctx)) return false;
|
|
4894
|
+
if (isWorkspacePackage(ctx, "@bensandee/agent-cli")) return false;
|
|
4895
|
+
return !readUnmanagedDevDepNames(ctx).has(AGENT_CLI_PACKAGE);
|
|
4896
|
+
}
|
|
4897
|
+
function isWorkspacePackage(ctx, packageName) {
|
|
4898
|
+
if (ctx.config.structure !== "monorepo") return false;
|
|
4899
|
+
const packagesDir = path.join(ctx.targetDir, "packages");
|
|
4900
|
+
if (!existsSync(packagesDir)) return false;
|
|
4901
|
+
try {
|
|
4902
|
+
for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
|
|
4903
|
+
if (!entry.isDirectory()) continue;
|
|
4904
|
+
const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
|
|
4905
|
+
if (!existsSync(pkgJsonPath)) continue;
|
|
4906
|
+
try {
|
|
4907
|
+
if (parsePackageJson(readFileSync(pkgJsonPath, "utf-8"))?.name === packageName) return true;
|
|
4908
|
+
} catch (_error) {}
|
|
4909
|
+
}
|
|
4910
|
+
} catch (_error) {}
|
|
4911
|
+
return false;
|
|
4912
|
+
}
|
|
4913
|
+
const UnmanagedSliceSchema = z.object({ unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional() });
|
|
4914
|
+
function readUnmanagedDevDepNames(ctx) {
|
|
4915
|
+
const slice = parseToolingConfigSlice(ctx.read(".tooling.json"), UnmanagedSliceSchema, { block: "unmanagedDevDependencies" });
|
|
4916
|
+
return new Set(slice?.unmanagedDevDependencies ?? []);
|
|
4917
|
+
}
|
|
4918
|
+
//#endregion
|
|
4861
4919
|
//#region src/generators/package-json.ts
|
|
4862
4920
|
const NODE_ENGINES_FLOOR = "24.13.0";
|
|
4863
4921
|
const ENGINES_NODE = `>=${NODE_ENGINES_FLOOR}`;
|
|
@@ -4966,18 +5024,18 @@ function perPackageDevDeps(config) {
|
|
|
4966
5024
|
"@types/node": "24.19.1",
|
|
4967
5025
|
"@vitest/coverage-v8": "5.0.3",
|
|
4968
5026
|
"commit-and-tag-version": "13.2.1",
|
|
4969
|
-
"knip": "6.
|
|
4970
|
-
"lefthook": "2.1
|
|
4971
|
-
"modern-web-guidance": "0.0.
|
|
4972
|
-
"oxfmt": "0.
|
|
4973
|
-
"oxlint": "1.
|
|
5027
|
+
"knip": "6.41.0",
|
|
5028
|
+
"lefthook": "2.2.1",
|
|
5029
|
+
"modern-web-guidance": "0.0.193",
|
|
5030
|
+
"oxfmt": "0.72.0",
|
|
5031
|
+
"oxlint": "1.87.0",
|
|
4974
5032
|
"oxlint-tsgolint": "7.0.2003",
|
|
4975
5033
|
"prettier": "3.9.9",
|
|
4976
|
-
"release-it": "21.1.
|
|
5034
|
+
"release-it": "21.1.1",
|
|
4977
5035
|
"tsdown": "0.23.0",
|
|
4978
5036
|
"typescript": "6.0.3",
|
|
4979
5037
|
"vitest": "5.0.3",
|
|
4980
|
-
"pnpm": "11.28.
|
|
5038
|
+
"pnpm": "11.28.5"
|
|
4981
5039
|
}["typescript"];
|
|
4982
5040
|
deps["@types/node"] = {
|
|
4983
5041
|
"@changesets/cli": "3.0.3",
|
|
@@ -4985,18 +5043,18 @@ function perPackageDevDeps(config) {
|
|
|
4985
5043
|
"@types/node": "24.19.1",
|
|
4986
5044
|
"@vitest/coverage-v8": "5.0.3",
|
|
4987
5045
|
"commit-and-tag-version": "13.2.1",
|
|
4988
|
-
"knip": "6.
|
|
4989
|
-
"lefthook": "2.1
|
|
4990
|
-
"modern-web-guidance": "0.0.
|
|
4991
|
-
"oxfmt": "0.
|
|
4992
|
-
"oxlint": "1.
|
|
5046
|
+
"knip": "6.41.0",
|
|
5047
|
+
"lefthook": "2.2.1",
|
|
5048
|
+
"modern-web-guidance": "0.0.193",
|
|
5049
|
+
"oxfmt": "0.72.0",
|
|
5050
|
+
"oxlint": "1.87.0",
|
|
4993
5051
|
"oxlint-tsgolint": "7.0.2003",
|
|
4994
5052
|
"prettier": "3.9.9",
|
|
4995
|
-
"release-it": "21.1.
|
|
5053
|
+
"release-it": "21.1.1",
|
|
4996
5054
|
"tsdown": "0.23.0",
|
|
4997
5055
|
"typescript": "6.0.3",
|
|
4998
5056
|
"vitest": "5.0.3",
|
|
4999
|
-
"pnpm": "11.28.
|
|
5057
|
+
"pnpm": "11.28.5"
|
|
5000
5058
|
}["@types/node"];
|
|
5001
5059
|
}
|
|
5002
5060
|
if (config.builder === "tsdown") deps["tsdown"] = {
|
|
@@ -5005,18 +5063,18 @@ function perPackageDevDeps(config) {
|
|
|
5005
5063
|
"@types/node": "24.19.1",
|
|
5006
5064
|
"@vitest/coverage-v8": "5.0.3",
|
|
5007
5065
|
"commit-and-tag-version": "13.2.1",
|
|
5008
|
-
"knip": "6.
|
|
5009
|
-
"lefthook": "2.1
|
|
5010
|
-
"modern-web-guidance": "0.0.
|
|
5011
|
-
"oxfmt": "0.
|
|
5012
|
-
"oxlint": "1.
|
|
5066
|
+
"knip": "6.41.0",
|
|
5067
|
+
"lefthook": "2.2.1",
|
|
5068
|
+
"modern-web-guidance": "0.0.193",
|
|
5069
|
+
"oxfmt": "0.72.0",
|
|
5070
|
+
"oxlint": "1.87.0",
|
|
5013
5071
|
"oxlint-tsgolint": "7.0.2003",
|
|
5014
5072
|
"prettier": "3.9.9",
|
|
5015
|
-
"release-it": "21.1.
|
|
5073
|
+
"release-it": "21.1.1",
|
|
5016
5074
|
"tsdown": "0.23.0",
|
|
5017
5075
|
"typescript": "6.0.3",
|
|
5018
5076
|
"vitest": "5.0.3",
|
|
5019
|
-
"pnpm": "11.28.
|
|
5077
|
+
"pnpm": "11.28.5"
|
|
5020
5078
|
}["tsdown"];
|
|
5021
5079
|
return deps;
|
|
5022
5080
|
}
|
|
@@ -5028,18 +5086,18 @@ function vitestDevDeps() {
|
|
|
5028
5086
|
"@types/node": "24.19.1",
|
|
5029
5087
|
"@vitest/coverage-v8": "5.0.3",
|
|
5030
5088
|
"commit-and-tag-version": "13.2.1",
|
|
5031
|
-
"knip": "6.
|
|
5032
|
-
"lefthook": "2.1
|
|
5033
|
-
"modern-web-guidance": "0.0.
|
|
5034
|
-
"oxfmt": "0.
|
|
5035
|
-
"oxlint": "1.
|
|
5089
|
+
"knip": "6.41.0",
|
|
5090
|
+
"lefthook": "2.2.1",
|
|
5091
|
+
"modern-web-guidance": "0.0.193",
|
|
5092
|
+
"oxfmt": "0.72.0",
|
|
5093
|
+
"oxlint": "1.87.0",
|
|
5036
5094
|
"oxlint-tsgolint": "7.0.2003",
|
|
5037
5095
|
"prettier": "3.9.9",
|
|
5038
|
-
"release-it": "21.1.
|
|
5096
|
+
"release-it": "21.1.1",
|
|
5039
5097
|
"tsdown": "0.23.0",
|
|
5040
5098
|
"typescript": "6.0.3",
|
|
5041
5099
|
"vitest": "5.0.3",
|
|
5042
|
-
"pnpm": "11.28.
|
|
5100
|
+
"pnpm": "11.28.5"
|
|
5043
5101
|
}["vitest"],
|
|
5044
5102
|
"@vitest/coverage-v8": {
|
|
5045
5103
|
"@changesets/cli": "3.0.3",
|
|
@@ -5047,18 +5105,18 @@ function vitestDevDeps() {
|
|
|
5047
5105
|
"@types/node": "24.19.1",
|
|
5048
5106
|
"@vitest/coverage-v8": "5.0.3",
|
|
5049
5107
|
"commit-and-tag-version": "13.2.1",
|
|
5050
|
-
"knip": "6.
|
|
5051
|
-
"lefthook": "2.1
|
|
5052
|
-
"modern-web-guidance": "0.0.
|
|
5053
|
-
"oxfmt": "0.
|
|
5054
|
-
"oxlint": "1.
|
|
5108
|
+
"knip": "6.41.0",
|
|
5109
|
+
"lefthook": "2.2.1",
|
|
5110
|
+
"modern-web-guidance": "0.0.193",
|
|
5111
|
+
"oxfmt": "0.72.0",
|
|
5112
|
+
"oxlint": "1.87.0",
|
|
5055
5113
|
"oxlint-tsgolint": "7.0.2003",
|
|
5056
5114
|
"prettier": "3.9.9",
|
|
5057
|
-
"release-it": "21.1.
|
|
5115
|
+
"release-it": "21.1.1",
|
|
5058
5116
|
"tsdown": "0.23.0",
|
|
5059
5117
|
"typescript": "6.0.3",
|
|
5060
5118
|
"vitest": "5.0.3",
|
|
5061
|
-
"pnpm": "11.28.
|
|
5119
|
+
"pnpm": "11.28.5"
|
|
5062
5120
|
}["@vitest/coverage-v8"]
|
|
5063
5121
|
};
|
|
5064
5122
|
}
|
|
@@ -5069,18 +5127,18 @@ function rootDevDeps(config) {
|
|
|
5069
5127
|
"@types/node": "24.19.1",
|
|
5070
5128
|
"@vitest/coverage-v8": "5.0.3",
|
|
5071
5129
|
"commit-and-tag-version": "13.2.1",
|
|
5072
|
-
"knip": "6.
|
|
5073
|
-
"lefthook": "2.1
|
|
5074
|
-
"modern-web-guidance": "0.0.
|
|
5075
|
-
"oxfmt": "0.
|
|
5076
|
-
"oxlint": "1.
|
|
5130
|
+
"knip": "6.41.0",
|
|
5131
|
+
"lefthook": "2.2.1",
|
|
5132
|
+
"modern-web-guidance": "0.0.193",
|
|
5133
|
+
"oxfmt": "0.72.0",
|
|
5134
|
+
"oxlint": "1.87.0",
|
|
5077
5135
|
"oxlint-tsgolint": "7.0.2003",
|
|
5078
5136
|
"prettier": "3.9.9",
|
|
5079
|
-
"release-it": "21.1.
|
|
5137
|
+
"release-it": "21.1.1",
|
|
5080
5138
|
"tsdown": "0.23.0",
|
|
5081
5139
|
"typescript": "6.0.3",
|
|
5082
5140
|
"vitest": "5.0.3",
|
|
5083
|
-
"pnpm": "11.28.
|
|
5141
|
+
"pnpm": "11.28.5"
|
|
5084
5142
|
}["lefthook"] };
|
|
5085
5143
|
if (config.stack !== "none") {
|
|
5086
5144
|
deps["knip"] = {
|
|
@@ -5089,18 +5147,18 @@ function rootDevDeps(config) {
|
|
|
5089
5147
|
"@types/node": "24.19.1",
|
|
5090
5148
|
"@vitest/coverage-v8": "5.0.3",
|
|
5091
5149
|
"commit-and-tag-version": "13.2.1",
|
|
5092
|
-
"knip": "6.
|
|
5093
|
-
"lefthook": "2.1
|
|
5094
|
-
"modern-web-guidance": "0.0.
|
|
5095
|
-
"oxfmt": "0.
|
|
5096
|
-
"oxlint": "1.
|
|
5150
|
+
"knip": "6.41.0",
|
|
5151
|
+
"lefthook": "2.2.1",
|
|
5152
|
+
"modern-web-guidance": "0.0.193",
|
|
5153
|
+
"oxfmt": "0.72.0",
|
|
5154
|
+
"oxlint": "1.87.0",
|
|
5097
5155
|
"oxlint-tsgolint": "7.0.2003",
|
|
5098
5156
|
"prettier": "3.9.9",
|
|
5099
|
-
"release-it": "21.1.
|
|
5157
|
+
"release-it": "21.1.1",
|
|
5100
5158
|
"tsdown": "0.23.0",
|
|
5101
5159
|
"typescript": "6.0.3",
|
|
5102
5160
|
"vitest": "5.0.3",
|
|
5103
|
-
"pnpm": "11.28.
|
|
5161
|
+
"pnpm": "11.28.5"
|
|
5104
5162
|
}["knip"];
|
|
5105
5163
|
deps["oxlint"] = {
|
|
5106
5164
|
"@changesets/cli": "3.0.3",
|
|
@@ -5108,18 +5166,18 @@ function rootDevDeps(config) {
|
|
|
5108
5166
|
"@types/node": "24.19.1",
|
|
5109
5167
|
"@vitest/coverage-v8": "5.0.3",
|
|
5110
5168
|
"commit-and-tag-version": "13.2.1",
|
|
5111
|
-
"knip": "6.
|
|
5112
|
-
"lefthook": "2.1
|
|
5113
|
-
"modern-web-guidance": "0.0.
|
|
5114
|
-
"oxfmt": "0.
|
|
5115
|
-
"oxlint": "1.
|
|
5169
|
+
"knip": "6.41.0",
|
|
5170
|
+
"lefthook": "2.2.1",
|
|
5171
|
+
"modern-web-guidance": "0.0.193",
|
|
5172
|
+
"oxfmt": "0.72.0",
|
|
5173
|
+
"oxlint": "1.87.0",
|
|
5116
5174
|
"oxlint-tsgolint": "7.0.2003",
|
|
5117
5175
|
"prettier": "3.9.9",
|
|
5118
|
-
"release-it": "21.1.
|
|
5176
|
+
"release-it": "21.1.1",
|
|
5119
5177
|
"tsdown": "0.23.0",
|
|
5120
5178
|
"typescript": "6.0.3",
|
|
5121
5179
|
"vitest": "5.0.3",
|
|
5122
|
-
"pnpm": "11.28.
|
|
5180
|
+
"pnpm": "11.28.5"
|
|
5123
5181
|
}["oxlint"];
|
|
5124
5182
|
Object.assign(deps, vitestDevDeps());
|
|
5125
5183
|
}
|
|
@@ -5129,37 +5187,21 @@ function rootDevDeps(config) {
|
|
|
5129
5187
|
"@types/node": "24.19.1",
|
|
5130
5188
|
"@vitest/coverage-v8": "5.0.3",
|
|
5131
5189
|
"commit-and-tag-version": "13.2.1",
|
|
5132
|
-
"knip": "6.
|
|
5133
|
-
"lefthook": "2.1
|
|
5134
|
-
"modern-web-guidance": "0.0.
|
|
5135
|
-
"oxfmt": "0.
|
|
5136
|
-
"oxlint": "1.
|
|
5190
|
+
"knip": "6.41.0",
|
|
5191
|
+
"lefthook": "2.2.1",
|
|
5192
|
+
"modern-web-guidance": "0.0.193",
|
|
5193
|
+
"oxfmt": "0.72.0",
|
|
5194
|
+
"oxlint": "1.87.0",
|
|
5137
5195
|
"oxlint-tsgolint": "7.0.2003",
|
|
5138
5196
|
"prettier": "3.9.9",
|
|
5139
|
-
"release-it": "21.1.
|
|
5197
|
+
"release-it": "21.1.1",
|
|
5140
5198
|
"tsdown": "0.23.0",
|
|
5141
5199
|
"typescript": "6.0.3",
|
|
5142
5200
|
"vitest": "5.0.3",
|
|
5143
|
-
"pnpm": "11.28.
|
|
5201
|
+
"pnpm": "11.28.5"
|
|
5144
5202
|
}["oxlint-tsgolint"];
|
|
5145
5203
|
return deps;
|
|
5146
5204
|
}
|
|
5147
|
-
function isWorkspacePackage(ctx, packageName) {
|
|
5148
|
-
if (ctx.config.structure !== "monorepo") return false;
|
|
5149
|
-
const packagesDir = path.join(ctx.targetDir, "packages");
|
|
5150
|
-
if (!existsSync(packagesDir)) return false;
|
|
5151
|
-
try {
|
|
5152
|
-
for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
|
|
5153
|
-
if (!entry.isDirectory()) continue;
|
|
5154
|
-
const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
|
|
5155
|
-
if (!existsSync(pkgJsonPath)) continue;
|
|
5156
|
-
try {
|
|
5157
|
-
if (parsePackageJson(readFileSync(pkgJsonPath, "utf-8"))?.name === packageName) return true;
|
|
5158
|
-
} catch (_error) {}
|
|
5159
|
-
}
|
|
5160
|
-
} catch (_error) {}
|
|
5161
|
-
return false;
|
|
5162
|
-
}
|
|
5163
5205
|
function addReleaseDeps(deps, config) {
|
|
5164
5206
|
switch (config.releaseStrategy) {
|
|
5165
5207
|
case "release-it":
|
|
@@ -5169,18 +5211,18 @@ function addReleaseDeps(deps, config) {
|
|
|
5169
5211
|
"@types/node": "24.19.1",
|
|
5170
5212
|
"@vitest/coverage-v8": "5.0.3",
|
|
5171
5213
|
"commit-and-tag-version": "13.2.1",
|
|
5172
|
-
"knip": "6.
|
|
5173
|
-
"lefthook": "2.1
|
|
5174
|
-
"modern-web-guidance": "0.0.
|
|
5175
|
-
"oxfmt": "0.
|
|
5176
|
-
"oxlint": "1.
|
|
5214
|
+
"knip": "6.41.0",
|
|
5215
|
+
"lefthook": "2.2.1",
|
|
5216
|
+
"modern-web-guidance": "0.0.193",
|
|
5217
|
+
"oxfmt": "0.72.0",
|
|
5218
|
+
"oxlint": "1.87.0",
|
|
5177
5219
|
"oxlint-tsgolint": "7.0.2003",
|
|
5178
5220
|
"prettier": "3.9.9",
|
|
5179
|
-
"release-it": "21.1.
|
|
5221
|
+
"release-it": "21.1.1",
|
|
5180
5222
|
"tsdown": "0.23.0",
|
|
5181
5223
|
"typescript": "6.0.3",
|
|
5182
5224
|
"vitest": "5.0.3",
|
|
5183
|
-
"pnpm": "11.28.
|
|
5225
|
+
"pnpm": "11.28.5"
|
|
5184
5226
|
}["release-it"];
|
|
5185
5227
|
if (config.structure === "monorepo") deps["@release-it/bumper"] = {
|
|
5186
5228
|
"@changesets/cli": "3.0.3",
|
|
@@ -5188,18 +5230,18 @@ function addReleaseDeps(deps, config) {
|
|
|
5188
5230
|
"@types/node": "24.19.1",
|
|
5189
5231
|
"@vitest/coverage-v8": "5.0.3",
|
|
5190
5232
|
"commit-and-tag-version": "13.2.1",
|
|
5191
|
-
"knip": "6.
|
|
5192
|
-
"lefthook": "2.1
|
|
5193
|
-
"modern-web-guidance": "0.0.
|
|
5194
|
-
"oxfmt": "0.
|
|
5195
|
-
"oxlint": "1.
|
|
5233
|
+
"knip": "6.41.0",
|
|
5234
|
+
"lefthook": "2.2.1",
|
|
5235
|
+
"modern-web-guidance": "0.0.193",
|
|
5236
|
+
"oxfmt": "0.72.0",
|
|
5237
|
+
"oxlint": "1.87.0",
|
|
5196
5238
|
"oxlint-tsgolint": "7.0.2003",
|
|
5197
5239
|
"prettier": "3.9.9",
|
|
5198
|
-
"release-it": "21.1.
|
|
5240
|
+
"release-it": "21.1.1",
|
|
5199
5241
|
"tsdown": "0.23.0",
|
|
5200
5242
|
"typescript": "6.0.3",
|
|
5201
5243
|
"vitest": "5.0.3",
|
|
5202
|
-
"pnpm": "11.28.
|
|
5244
|
+
"pnpm": "11.28.5"
|
|
5203
5245
|
}["@release-it/bumper"];
|
|
5204
5246
|
break;
|
|
5205
5247
|
case "simple":
|
|
@@ -5209,18 +5251,18 @@ function addReleaseDeps(deps, config) {
|
|
|
5209
5251
|
"@types/node": "24.19.1",
|
|
5210
5252
|
"@vitest/coverage-v8": "5.0.3",
|
|
5211
5253
|
"commit-and-tag-version": "13.2.1",
|
|
5212
|
-
"knip": "6.
|
|
5213
|
-
"lefthook": "2.1
|
|
5214
|
-
"modern-web-guidance": "0.0.
|
|
5215
|
-
"oxfmt": "0.
|
|
5216
|
-
"oxlint": "1.
|
|
5254
|
+
"knip": "6.41.0",
|
|
5255
|
+
"lefthook": "2.2.1",
|
|
5256
|
+
"modern-web-guidance": "0.0.193",
|
|
5257
|
+
"oxfmt": "0.72.0",
|
|
5258
|
+
"oxlint": "1.87.0",
|
|
5217
5259
|
"oxlint-tsgolint": "7.0.2003",
|
|
5218
5260
|
"prettier": "3.9.9",
|
|
5219
|
-
"release-it": "21.1.
|
|
5261
|
+
"release-it": "21.1.1",
|
|
5220
5262
|
"tsdown": "0.23.0",
|
|
5221
5263
|
"typescript": "6.0.3",
|
|
5222
5264
|
"vitest": "5.0.3",
|
|
5223
|
-
"pnpm": "11.28.
|
|
5265
|
+
"pnpm": "11.28.5"
|
|
5224
5266
|
}["commit-and-tag-version"];
|
|
5225
5267
|
break;
|
|
5226
5268
|
case "changesets": deps["@changesets/cli"] = {
|
|
@@ -5229,18 +5271,18 @@ function addReleaseDeps(deps, config) {
|
|
|
5229
5271
|
"@types/node": "24.19.1",
|
|
5230
5272
|
"@vitest/coverage-v8": "5.0.3",
|
|
5231
5273
|
"commit-and-tag-version": "13.2.1",
|
|
5232
|
-
"knip": "6.
|
|
5233
|
-
"lefthook": "2.1
|
|
5234
|
-
"modern-web-guidance": "0.0.
|
|
5235
|
-
"oxfmt": "0.
|
|
5236
|
-
"oxlint": "1.
|
|
5274
|
+
"knip": "6.41.0",
|
|
5275
|
+
"lefthook": "2.2.1",
|
|
5276
|
+
"modern-web-guidance": "0.0.193",
|
|
5277
|
+
"oxfmt": "0.72.0",
|
|
5278
|
+
"oxlint": "1.87.0",
|
|
5237
5279
|
"oxlint-tsgolint": "7.0.2003",
|
|
5238
5280
|
"prettier": "3.9.9",
|
|
5239
|
-
"release-it": "21.1.
|
|
5281
|
+
"release-it": "21.1.1",
|
|
5240
5282
|
"tsdown": "0.23.0",
|
|
5241
5283
|
"typescript": "6.0.3",
|
|
5242
5284
|
"vitest": "5.0.3",
|
|
5243
|
-
"pnpm": "11.28.
|
|
5285
|
+
"pnpm": "11.28.5"
|
|
5244
5286
|
}["@changesets/cli"];
|
|
5245
5287
|
}
|
|
5246
5288
|
}
|
|
@@ -5307,10 +5349,6 @@ function reclaimManagedDevDeps(ctx, pkg, managedDevDeps, unmanaged, changes) {
|
|
|
5307
5349
|
}
|
|
5308
5350
|
pkg.devDependencies = existingDevDeps;
|
|
5309
5351
|
}
|
|
5310
|
-
function readUnmanagedDevDeps(ctx) {
|
|
5311
|
-
const slice = parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional() }), { block: "unmanagedDevDependencies" });
|
|
5312
|
-
return new Set(slice?.unmanagedDevDependencies ?? []);
|
|
5313
|
-
}
|
|
5314
5352
|
function isAsciiCheckEnabled(ctx) {
|
|
5315
5353
|
return parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ ascii: AsciiConfigSchema.optional() }), { block: "ascii" })?.ascii?.enabled === true;
|
|
5316
5354
|
}
|
|
@@ -5345,18 +5383,18 @@ function applyMetadataDefaults(pkg, ctx, changes) {
|
|
|
5345
5383
|
"@types/node": "24.19.1",
|
|
5346
5384
|
"@vitest/coverage-v8": "5.0.3",
|
|
5347
5385
|
"commit-and-tag-version": "13.2.1",
|
|
5348
|
-
"knip": "6.
|
|
5349
|
-
"lefthook": "2.1
|
|
5350
|
-
"modern-web-guidance": "0.0.
|
|
5351
|
-
"oxfmt": "0.
|
|
5352
|
-
"oxlint": "1.
|
|
5386
|
+
"knip": "6.41.0",
|
|
5387
|
+
"lefthook": "2.2.1",
|
|
5388
|
+
"modern-web-guidance": "0.0.193",
|
|
5389
|
+
"oxfmt": "0.72.0",
|
|
5390
|
+
"oxlint": "1.87.0",
|
|
5353
5391
|
"oxlint-tsgolint": "7.0.2003",
|
|
5354
5392
|
"prettier": "3.9.9",
|
|
5355
|
-
"release-it": "21.1.
|
|
5393
|
+
"release-it": "21.1.1",
|
|
5356
5394
|
"tsdown": "0.23.0",
|
|
5357
5395
|
"typescript": "6.0.3",
|
|
5358
5396
|
"vitest": "5.0.3",
|
|
5359
|
-
"pnpm": "11.28.
|
|
5397
|
+
"pnpm": "11.28.5"
|
|
5360
5398
|
}["pnpm"];
|
|
5361
5399
|
const desiredPm = pinnedPnpm ? `pnpm@${pinnedPnpm}` : void 0;
|
|
5362
5400
|
if (desiredPm) {
|
|
@@ -5395,26 +5433,27 @@ function buildManagedDevDeps(ctx) {
|
|
|
5395
5433
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
5396
5434
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
5397
5435
|
if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
|
|
5398
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.
|
|
5436
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.91.0";
|
|
5399
5437
|
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
|
|
5438
|
+
if (wantsAgentCli(ctx)) devDeps[AGENT_CLI_PACKAGE] = "0.1.2";
|
|
5400
5439
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
5401
5440
|
"@changesets/cli": "3.0.3",
|
|
5402
5441
|
"@release-it/bumper": "8.0.1",
|
|
5403
5442
|
"@types/node": "24.19.1",
|
|
5404
5443
|
"@vitest/coverage-v8": "5.0.3",
|
|
5405
5444
|
"commit-and-tag-version": "13.2.1",
|
|
5406
|
-
"knip": "6.
|
|
5407
|
-
"lefthook": "2.1
|
|
5408
|
-
"modern-web-guidance": "0.0.
|
|
5409
|
-
"oxfmt": "0.
|
|
5410
|
-
"oxlint": "1.
|
|
5445
|
+
"knip": "6.41.0",
|
|
5446
|
+
"lefthook": "2.2.1",
|
|
5447
|
+
"modern-web-guidance": "0.0.193",
|
|
5448
|
+
"oxfmt": "0.72.0",
|
|
5449
|
+
"oxlint": "1.87.0",
|
|
5411
5450
|
"oxlint-tsgolint": "7.0.2003",
|
|
5412
5451
|
"prettier": "3.9.9",
|
|
5413
|
-
"release-it": "21.1.
|
|
5452
|
+
"release-it": "21.1.1",
|
|
5414
5453
|
"tsdown": "0.23.0",
|
|
5415
5454
|
"typescript": "6.0.3",
|
|
5416
5455
|
"vitest": "5.0.3",
|
|
5417
|
-
"pnpm": "11.28.
|
|
5456
|
+
"pnpm": "11.28.5"
|
|
5418
5457
|
}["oxfmt"];
|
|
5419
5458
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
|
|
5420
5459
|
"@changesets/cli": "3.0.3",
|
|
@@ -5422,18 +5461,18 @@ function buildManagedDevDeps(ctx) {
|
|
|
5422
5461
|
"@types/node": "24.19.1",
|
|
5423
5462
|
"@vitest/coverage-v8": "5.0.3",
|
|
5424
5463
|
"commit-and-tag-version": "13.2.1",
|
|
5425
|
-
"knip": "6.
|
|
5426
|
-
"lefthook": "2.1
|
|
5427
|
-
"modern-web-guidance": "0.0.
|
|
5428
|
-
"oxfmt": "0.
|
|
5429
|
-
"oxlint": "1.
|
|
5464
|
+
"knip": "6.41.0",
|
|
5465
|
+
"lefthook": "2.2.1",
|
|
5466
|
+
"modern-web-guidance": "0.0.193",
|
|
5467
|
+
"oxfmt": "0.72.0",
|
|
5468
|
+
"oxlint": "1.87.0",
|
|
5430
5469
|
"oxlint-tsgolint": "7.0.2003",
|
|
5431
5470
|
"prettier": "3.9.9",
|
|
5432
|
-
"release-it": "21.1.
|
|
5471
|
+
"release-it": "21.1.1",
|
|
5433
5472
|
"tsdown": "0.23.0",
|
|
5434
5473
|
"typescript": "6.0.3",
|
|
5435
5474
|
"vitest": "5.0.3",
|
|
5436
|
-
"pnpm": "11.28.
|
|
5475
|
+
"pnpm": "11.28.5"
|
|
5437
5476
|
}["prettier"];
|
|
5438
5477
|
addReleaseDeps(devDeps, ctx.config);
|
|
5439
5478
|
return devDeps;
|
|
@@ -5442,7 +5481,7 @@ async function generatePackageJson(ctx) {
|
|
|
5442
5481
|
const filePath = "package.json";
|
|
5443
5482
|
const existing = ctx.read(filePath);
|
|
5444
5483
|
const allScripts = buildManagedScripts(ctx);
|
|
5445
|
-
const unmanaged =
|
|
5484
|
+
const unmanaged = readUnmanagedDevDepNames(ctx);
|
|
5446
5485
|
const devDeps = buildManagedDevDeps(ctx);
|
|
5447
5486
|
if (existing) {
|
|
5448
5487
|
const pkg = parsePackageJson(existing);
|
|
@@ -5492,18 +5531,18 @@ async function generatePackageJson(ctx) {
|
|
|
5492
5531
|
"@types/node": "24.19.1",
|
|
5493
5532
|
"@vitest/coverage-v8": "5.0.3",
|
|
5494
5533
|
"commit-and-tag-version": "13.2.1",
|
|
5495
|
-
"knip": "6.
|
|
5496
|
-
"lefthook": "2.1
|
|
5497
|
-
"modern-web-guidance": "0.0.
|
|
5498
|
-
"oxfmt": "0.
|
|
5499
|
-
"oxlint": "1.
|
|
5534
|
+
"knip": "6.41.0",
|
|
5535
|
+
"lefthook": "2.2.1",
|
|
5536
|
+
"modern-web-guidance": "0.0.193",
|
|
5537
|
+
"oxfmt": "0.72.0",
|
|
5538
|
+
"oxlint": "1.87.0",
|
|
5500
5539
|
"oxlint-tsgolint": "7.0.2003",
|
|
5501
5540
|
"prettier": "3.9.9",
|
|
5502
|
-
"release-it": "21.1.
|
|
5541
|
+
"release-it": "21.1.1",
|
|
5503
5542
|
"tsdown": "0.23.0",
|
|
5504
5543
|
"typescript": "6.0.3",
|
|
5505
5544
|
"vitest": "5.0.3",
|
|
5506
|
-
"pnpm": "11.28.
|
|
5545
|
+
"pnpm": "11.28.5"
|
|
5507
5546
|
}["pnpm"]}`
|
|
5508
5547
|
};
|
|
5509
5548
|
ctx.write(filePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
@@ -7318,6 +7357,59 @@ async function generateGitattributes(ctx) {
|
|
|
7318
7357
|
};
|
|
7319
7358
|
}
|
|
7320
7359
|
//#endregion
|
|
7360
|
+
//#region src/default-branch.ts
|
|
7361
|
+
const DEFAULT_BRANCH = "main";
|
|
7362
|
+
const DefaultBranchSliceSchema = z.object({ defaultBranch: z.union([z.literal(""), DefaultBranchNameSchema]).optional() });
|
|
7363
|
+
function configuredDefaultBranch(raw) {
|
|
7364
|
+
const configured = parseToolingConfigSlice(raw, DefaultBranchSliceSchema, { block: "defaultBranch" })?.defaultBranch;
|
|
7365
|
+
if (configured === void 0 || configured === "") return DEFAULT_BRANCH;
|
|
7366
|
+
return configured;
|
|
7367
|
+
}
|
|
7368
|
+
function readConfiguredDefaultBranch(dir) {
|
|
7369
|
+
const configPath = path.join(dir, ".tooling.json");
|
|
7370
|
+
if (!existsSync(configPath)) return DEFAULT_BRANCH;
|
|
7371
|
+
let raw;
|
|
7372
|
+
try {
|
|
7373
|
+
raw = readFileSync(configPath, "utf-8");
|
|
7374
|
+
} catch (error) {
|
|
7375
|
+
throw new FatalError(`Cannot read ${configPath}: ${describeError$1(error)}`, { cause: error });
|
|
7376
|
+
}
|
|
7377
|
+
return configuredDefaultBranch(raw);
|
|
7378
|
+
}
|
|
7379
|
+
function processCiEventSource() {
|
|
7380
|
+
return {
|
|
7381
|
+
env: process.env,
|
|
7382
|
+
readFile: (absolutePath) => {
|
|
7383
|
+
try {
|
|
7384
|
+
return readFileSync(absolutePath, "utf-8");
|
|
7385
|
+
} catch (_error) {
|
|
7386
|
+
return;
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7389
|
+
};
|
|
7390
|
+
}
|
|
7391
|
+
const CiEventSchema = z.object({ repository: z.object({ default_branch: z.string().min(1) }) });
|
|
7392
|
+
function ciDefaultBranch(source) {
|
|
7393
|
+
if (source === void 0 || source.env["GITHUB_ACTIONS"] !== "true") return void 0;
|
|
7394
|
+
const eventPath = source.env["GITHUB_EVENT_PATH"];
|
|
7395
|
+
if (eventPath === void 0 || eventPath === "") return void 0;
|
|
7396
|
+
const raw = source.readFile(eventPath);
|
|
7397
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
7398
|
+
let json;
|
|
7399
|
+
try {
|
|
7400
|
+
json = JSON.parse(raw);
|
|
7401
|
+
} catch (_error) {
|
|
7402
|
+
return;
|
|
7403
|
+
}
|
|
7404
|
+
const result = CiEventSchema.safeParse(json);
|
|
7405
|
+
return result.success ? result.data.repository.default_branch : void 0;
|
|
7406
|
+
}
|
|
7407
|
+
function refuseForgeDefaultMismatch(configured, source, command) {
|
|
7408
|
+
const reported = ciDefaultBranch(source);
|
|
7409
|
+
if (reported === void 0 || reported === configured) return;
|
|
7410
|
+
throw new FatalError(`${command}: .tooling.json#defaultBranch is "${configured}", but the forge reports "${reported}" as this repository's default branch. Set .tooling.json#defaultBranch to "${reported}" and run bst repo:sync.`);
|
|
7411
|
+
}
|
|
7412
|
+
//#endregion
|
|
7321
7413
|
//#region src/generators/release-ci.ts
|
|
7322
7414
|
const DEFAULT_POST_RELEASE_COMMAND = [
|
|
7323
7415
|
"pnpm exec bst webhook:send",
|
|
@@ -7337,7 +7429,7 @@ function hookSecretEnv(secrets) {
|
|
|
7337
7429
|
};
|
|
7338
7430
|
}
|
|
7339
7431
|
function postReleaseHookStep(opts) {
|
|
7340
|
-
const ifCondition = opts?.condition ?? (opts?.guardMain ?
|
|
7432
|
+
const ifCondition = opts?.condition ?? (opts?.guardMain ? isDefaultBranch(opts.defaultBranch) : void 0);
|
|
7341
7433
|
return { step: {
|
|
7342
7434
|
name: "Post-release hook",
|
|
7343
7435
|
...ifCondition && { if: ifCondition },
|
|
@@ -7348,7 +7440,7 @@ function postReleaseHookStep(opts) {
|
|
|
7348
7440
|
function postReleaseHookJob(opts) {
|
|
7349
7441
|
return {
|
|
7350
7442
|
needs: opts.needsJob,
|
|
7351
|
-
if: onlyOnDefaultBranch(opts.publishCondition),
|
|
7443
|
+
if: onlyOnDefaultBranch(opts.defaultBranch, opts.publishCondition),
|
|
7352
7444
|
"continue-on-error": true,
|
|
7353
7445
|
steps: [...commonSteps(opts.ci), { step: {
|
|
7354
7446
|
name: "Post-release hook",
|
|
@@ -7492,6 +7584,7 @@ function releaseWorkflowPath(ci) {
|
|
|
7492
7584
|
async function generateReleaseCi(ctx) {
|
|
7493
7585
|
const filePath = "release-ci";
|
|
7494
7586
|
const isGitHub = ctx.config.ci === "github";
|
|
7587
|
+
const defaultBranch = configuredDefaultBranch(ctx.read(".tooling.json"));
|
|
7495
7588
|
if (ctx.config.ci === "none" || ctx.config.releaseStrategy === "none") return {
|
|
7496
7589
|
filePath,
|
|
7497
7590
|
action: "skipped",
|
|
@@ -7535,6 +7628,7 @@ async function generateReleaseCi(ctx) {
|
|
|
7535
7628
|
let extraJobs;
|
|
7536
7629
|
let jobOutputs;
|
|
7537
7630
|
if (ctx.config.releaseStrategy === "release-it") steps.push(postReleaseHookStep({
|
|
7631
|
+
defaultBranch,
|
|
7538
7632
|
command: getPostReleaseHookCommand(ctx),
|
|
7539
7633
|
secrets: hookSecrets
|
|
7540
7634
|
}));
|
|
@@ -7544,6 +7638,7 @@ async function generateReleaseCi(ctx) {
|
|
|
7544
7638
|
tag: actionsExpr("steps.release.outputs.tag")
|
|
7545
7639
|
};
|
|
7546
7640
|
extraJobs = { "post-release-hook": postReleaseHookJob({
|
|
7641
|
+
defaultBranch,
|
|
7547
7642
|
ci: ctx.config.ci,
|
|
7548
7643
|
needsJob: "release",
|
|
7549
7644
|
publishCondition: "needs.release.outputs.pushed == 'true'",
|
|
@@ -7559,7 +7654,7 @@ async function generateReleaseCi(ctx) {
|
|
|
7559
7654
|
on,
|
|
7560
7655
|
...isGitHub && { permissions: { contents: "write" } },
|
|
7561
7656
|
jobName: "release",
|
|
7562
|
-
jobIf:
|
|
7657
|
+
jobIf: isDefaultBranch(defaultBranch),
|
|
7563
7658
|
...jobOutputs && { jobOutputs },
|
|
7564
7659
|
steps,
|
|
7565
7660
|
...extraJobs && { extraJobs }
|
|
@@ -7590,7 +7685,9 @@ async function generateReleaseCi(ctx) {
|
|
|
7590
7685
|
}
|
|
7591
7686
|
//#endregion
|
|
7592
7687
|
//#region src/release/version-branch.ts
|
|
7593
|
-
|
|
7688
|
+
function versionPrBranch(defaultBranch) {
|
|
7689
|
+
return `changeset-release/${defaultBranch}`;
|
|
7690
|
+
}
|
|
7594
7691
|
//#endregion
|
|
7595
7692
|
//#region src/generators/ci.ts
|
|
7596
7693
|
function coverageHistorySteps() {
|
|
@@ -7624,7 +7721,7 @@ function healthSteps(includeHistory) {
|
|
|
7624
7721
|
run: "pnpm exec bst health:status --limit 10"
|
|
7625
7722
|
} }];
|
|
7626
7723
|
}
|
|
7627
|
-
function changesetsReleaseStep(ci, publishesNpm, npmAuth) {
|
|
7724
|
+
function changesetsReleaseStep(ci, publishesNpm, npmAuth, defaultBranch) {
|
|
7628
7725
|
if (ci === "github") {
|
|
7629
7726
|
const changesetsEnv = {
|
|
7630
7727
|
GITHUB_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
@@ -7633,7 +7730,7 @@ function changesetsReleaseStep(ci, publishesNpm, npmAuth) {
|
|
|
7633
7730
|
return { step: {
|
|
7634
7731
|
id: "release",
|
|
7635
7732
|
uses: "changesets/action@v1",
|
|
7636
|
-
if:
|
|
7733
|
+
if: isDefaultBranch(defaultBranch),
|
|
7637
7734
|
with: {
|
|
7638
7735
|
publish: "pnpm changeset publish",
|
|
7639
7736
|
version: "pnpm changeset version"
|
|
@@ -7651,19 +7748,19 @@ function changesetsReleaseStep(ci, publishesNpm, npmAuth) {
|
|
|
7651
7748
|
return { step: {
|
|
7652
7749
|
id: "release",
|
|
7653
7750
|
name: "Release",
|
|
7654
|
-
if:
|
|
7751
|
+
if: isDefaultBranch(defaultBranch),
|
|
7655
7752
|
env: releaseEnv,
|
|
7656
7753
|
run: npmAuth.publishRun("pnpm exec bst release:changesets")
|
|
7657
7754
|
} };
|
|
7658
7755
|
}
|
|
7659
|
-
function ciConcurrency(ci) {
|
|
7756
|
+
function ciConcurrency(ci, defaultBranch) {
|
|
7660
7757
|
if (ci === "forgejo") return {
|
|
7661
|
-
group: `ci-${actionsExpr(`${
|
|
7662
|
-
"cancel-in-progress": actionsExpr(
|
|
7758
|
+
group: `ci-${actionsExpr(`${isDefaultBranch(defaultBranch)} && github.sha || github.ref`)}`,
|
|
7759
|
+
"cancel-in-progress": actionsExpr(isNotDefaultBranch(defaultBranch))
|
|
7663
7760
|
};
|
|
7664
7761
|
return {
|
|
7665
7762
|
group: `ci-${actionsExpr("github.ref")}`,
|
|
7666
|
-
"cancel-in-progress": actionsExpr(
|
|
7763
|
+
"cancel-in-progress": actionsExpr(isNotDefaultBranch(defaultBranch))
|
|
7667
7764
|
};
|
|
7668
7765
|
}
|
|
7669
7766
|
function checkSteps(ci, coverageEnabled, emitDockerSweepStep) {
|
|
@@ -7694,6 +7791,7 @@ async function generateCi(ctx) {
|
|
|
7694
7791
|
action: "skipped",
|
|
7695
7792
|
description: "CI workflow not requested"
|
|
7696
7793
|
};
|
|
7794
|
+
const defaultBranch = configuredDefaultBranch(ctx.read(".tooling.json"));
|
|
7697
7795
|
const isGitHub = ctx.config.ci === "github";
|
|
7698
7796
|
const isForgejo = !isGitHub;
|
|
7699
7797
|
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
@@ -7708,7 +7806,7 @@ async function generateCi(ctx) {
|
|
|
7708
7806
|
const hasAssets = isChangesets && (hasReleaseAssets(ctx) || hasVersionStamp(ctx));
|
|
7709
7807
|
const host = hasDocker ? resolveGenTimeHost(ctx) : null;
|
|
7710
7808
|
const publishedIf = "steps.release.outputs.published == 'true'";
|
|
7711
|
-
const
|
|
7809
|
+
const publishedOnDefaultIf = onlyOnDefaultBranch(defaultBranch, publishedIf);
|
|
7712
7810
|
const githubChangesetsDeriveTagStep = { step: {
|
|
7713
7811
|
name: "Derive release tag",
|
|
7714
7812
|
id: "derive-tag",
|
|
@@ -7729,17 +7827,17 @@ async function generateCi(ctx) {
|
|
|
7729
7827
|
...checkSteps(ctx.config.ci, coverage.enabled, hasDockerPackages(ctx)),
|
|
7730
7828
|
...emitCoverageSteps ? coverageHistorySteps() : [],
|
|
7731
7829
|
...healthSteps(emitHealthHistory),
|
|
7732
|
-
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm, npmAuth)] : [],
|
|
7830
|
+
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm, npmAuth, defaultBranch)] : [],
|
|
7733
7831
|
...isChangesets && isGitHub ? [githubChangesetsDeriveTagStep] : [],
|
|
7734
7832
|
...hasAssets ? [{ step: {
|
|
7735
7833
|
name: "Upload Release Assets",
|
|
7736
|
-
if:
|
|
7834
|
+
if: publishedOnDefaultIf,
|
|
7737
7835
|
env: releaseEnv,
|
|
7738
7836
|
run: "pnpm exec bst release:assets"
|
|
7739
7837
|
} }] : [],
|
|
7740
7838
|
...hasDocker ? [{ step: {
|
|
7741
7839
|
name: "Publish Docker images",
|
|
7742
|
-
if:
|
|
7840
|
+
if: publishedOnDefaultIf,
|
|
7743
7841
|
env: dockerPublishEnv(host),
|
|
7744
7842
|
run: "pnpm exec bst docker:publish"
|
|
7745
7843
|
} }] : [],
|
|
@@ -7747,7 +7845,7 @@ async function generateCi(ctx) {
|
|
|
7747
7845
|
deploy,
|
|
7748
7846
|
packageName: ctx.config.name,
|
|
7749
7847
|
releaseEnv,
|
|
7750
|
-
condition:
|
|
7848
|
+
condition: publishedOnDefaultIf,
|
|
7751
7849
|
secrets: hookSecrets
|
|
7752
7850
|
})] : []
|
|
7753
7851
|
];
|
|
@@ -7759,6 +7857,7 @@ async function generateCi(ctx) {
|
|
|
7759
7857
|
tag: changesetsTagOutputExpr
|
|
7760
7858
|
};
|
|
7761
7859
|
extraJobs = { "post-release-hook": postReleaseHookJob({
|
|
7860
|
+
defaultBranch,
|
|
7762
7861
|
ci: ctx.config.ci,
|
|
7763
7862
|
needsJob: "check",
|
|
7764
7863
|
publishCondition: "needs.check.outputs.published == 'true'",
|
|
@@ -7767,7 +7866,7 @@ async function generateCi(ctx) {
|
|
|
7767
7866
|
secrets: hookSecrets
|
|
7768
7867
|
}) };
|
|
7769
7868
|
}
|
|
7770
|
-
const checkJobEnv = emitCoverageSteps || emitHealthHistory ?
|
|
7869
|
+
const checkJobEnv = emitCoverageSteps || emitHealthHistory ? defaultBranchJobEnv(defaultBranch) : {};
|
|
7771
7870
|
const checksSlice = parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ checks: ChecksConfigSchema.optional() }), { block: "checks" });
|
|
7772
7871
|
const skipOnVersionPr = isChangesets && checksSlice?.checks?.runOnVersionPr !== true;
|
|
7773
7872
|
const content = buildWorkflowYaml({
|
|
@@ -7775,16 +7874,16 @@ async function generateCi(ctx) {
|
|
|
7775
7874
|
name: "CI",
|
|
7776
7875
|
on: {
|
|
7777
7876
|
push: {
|
|
7778
|
-
branches: [
|
|
7877
|
+
branches: [defaultBranch],
|
|
7779
7878
|
"tags-ignore": ["**"]
|
|
7780
7879
|
},
|
|
7781
7880
|
pull_request: null
|
|
7782
7881
|
},
|
|
7783
7882
|
...isGitHub && (emitCoverageSteps || emitHealthHistory) && { permissions: { contents: "write" } },
|
|
7784
7883
|
enableEmailNotifications: isForgejo,
|
|
7785
|
-
...isChangesets && { concurrency: ciConcurrency(ctx.config.ci) },
|
|
7884
|
+
...isChangesets && { concurrency: ciConcurrency(ctx.config.ci, defaultBranch) },
|
|
7786
7885
|
jobName: "check",
|
|
7787
|
-
...skipOnVersionPr && { jobIf: `github.head_ref != '
|
|
7886
|
+
...skipOnVersionPr && { jobIf: `github.head_ref != '${versionPrBranch(defaultBranch)}'` },
|
|
7788
7887
|
...Object.keys(checkJobEnv).length > 0 && { jobEnv: checkJobEnv },
|
|
7789
7888
|
...jobOutputs && { jobOutputs },
|
|
7790
7889
|
steps,
|
|
@@ -7828,19 +7927,25 @@ function foreignOwnedBinaries(ctx) {
|
|
|
7828
7927
|
function ignoreBinariesField(binaries) {
|
|
7829
7928
|
return binaries.length > 0 ? `\n ignoreBinaries: ${JSON.stringify(binaries)},` : "";
|
|
7830
7929
|
}
|
|
7831
|
-
function
|
|
7930
|
+
function ignorableDependencies(ctx) {
|
|
7931
|
+
return wantsAgentCli(ctx) ? [AGENT_CLI_PACKAGE] : [];
|
|
7932
|
+
}
|
|
7933
|
+
function ignoreDependenciesField(dependencies) {
|
|
7934
|
+
return dependencies.length > 0 ? `\n ignoreDependencies: ${JSON.stringify(dependencies)},` : "";
|
|
7935
|
+
}
|
|
7936
|
+
function buildKnipConfigSingle(ignoreBinaries, ignoreDependencies) {
|
|
7832
7937
|
return `import type { KnipConfig } from "knip";
|
|
7833
7938
|
|
|
7834
7939
|
export default {
|
|
7835
7940
|
entry: ["src/index.ts"],
|
|
7836
|
-
project: ["src/**/*.ts"],${ignoreBinariesField(ignoreBinaries)}
|
|
7941
|
+
project: ["src/**/*.ts"],${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(ignoreDependencies)}
|
|
7837
7942
|
} satisfies KnipConfig;
|
|
7838
7943
|
`;
|
|
7839
7944
|
}
|
|
7840
|
-
function buildKnipConfigMonorepo(ignoreBinaries) {
|
|
7945
|
+
function buildKnipConfigMonorepo(ignoreBinaries, ignoreDependencies) {
|
|
7841
7946
|
return `import type { KnipConfig } from "knip";
|
|
7842
7947
|
|
|
7843
|
-
export default {${ignoreBinariesField(ignoreBinaries)}
|
|
7948
|
+
export default {${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(ignoreDependencies)}
|
|
7844
7949
|
workspaces: {
|
|
7845
7950
|
".": {
|
|
7846
7951
|
entry: [],
|
|
@@ -7901,16 +8006,17 @@ function hookIsInProjectScope(content, path) {
|
|
|
7901
8006
|
if (patterns === void 0) return true;
|
|
7902
8007
|
return patterns.some((pattern) => picomatch(pattern, { dot: true })(HOOK_FILE));
|
|
7903
8008
|
}
|
|
7904
|
-
function ensureIgnorePattern(content, path, ignoreBinaries) {
|
|
8009
|
+
function ensureIgnorePattern(content, path, ignoreBinaries, ignoreDependencies) {
|
|
7905
8010
|
const needsHooksIgnore = hookIsInProjectScope(content, path);
|
|
7906
|
-
if (isJsonConfig(path)) return ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore);
|
|
7907
|
-
return ensureIgnoreInTs(content, ignoreBinaries, needsHooksIgnore);
|
|
8011
|
+
if (isJsonConfig(path)) return ensureIgnoreInJson(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore);
|
|
8012
|
+
return ensureIgnoreInTs(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore);
|
|
7908
8013
|
}
|
|
7909
8014
|
const KnipJsonSchema = z.object({
|
|
7910
8015
|
ignore: z.array(z.string()).optional(),
|
|
7911
|
-
ignoreBinaries: z.array(z.string()).optional()
|
|
8016
|
+
ignoreBinaries: z.array(z.string()).optional(),
|
|
8017
|
+
ignoreDependencies: z.array(z.string()).optional()
|
|
7912
8018
|
}).passthrough();
|
|
7913
|
-
function ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore) {
|
|
8019
|
+
function ensureIgnoreInJson(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore) {
|
|
7914
8020
|
let parsedJson;
|
|
7915
8021
|
try {
|
|
7916
8022
|
parsedJson = JSON.parse(content);
|
|
@@ -7931,6 +8037,12 @@ function ensureIgnoreInJson(content, ignoreBinaries, needsHooksIgnore) {
|
|
|
7931
8037
|
parsed.ignoreBinaries = [...parsed.ignoreBinaries ?? [], ...missingBinaries];
|
|
7932
8038
|
changed = true;
|
|
7933
8039
|
}
|
|
8040
|
+
const existingDependencies = new Set(parsed.ignoreDependencies ?? []);
|
|
8041
|
+
const missingDependencies = ignoreDependencies.filter((dependency) => !existingDependencies.has(dependency));
|
|
8042
|
+
if (missingDependencies.length > 0) {
|
|
8043
|
+
parsed.ignoreDependencies = [...parsed.ignoreDependencies ?? [], ...missingDependencies];
|
|
8044
|
+
changed = true;
|
|
8045
|
+
}
|
|
7934
8046
|
if (!changed) return void 0;
|
|
7935
8047
|
return JSON.stringify(parsed, null, 2) + "\n";
|
|
7936
8048
|
}
|
|
@@ -7940,7 +8052,30 @@ function missingIgnoreBinariesInTs(content, binaries) {
|
|
|
7940
8052
|
const arrayContent = match[1] ?? "";
|
|
7941
8053
|
return binaries.filter((binary) => !arrayContent.includes(`"${binary}"`));
|
|
7942
8054
|
}
|
|
7943
|
-
function
|
|
8055
|
+
function missingIgnoreDependenciesInTs(content, dependencies) {
|
|
8056
|
+
const match = /ignoreDependencies:\s*\[([^\]]*)\]/.exec(content);
|
|
8057
|
+
if (!match) return dependencies;
|
|
8058
|
+
const arrayContent = match[1] ?? "";
|
|
8059
|
+
return dependencies.filter((dependency) => !arrayContent.includes(`"${dependency}"`));
|
|
8060
|
+
}
|
|
8061
|
+
function insertIntoTsArray(content, field, entries) {
|
|
8062
|
+
const quoted = entries.map((entry) => `"${entry}"`).join(", ");
|
|
8063
|
+
const arrayPattern = new RegExp("(" + field + ":\\s*\\[)");
|
|
8064
|
+
if (arrayPattern.test(content)) return {
|
|
8065
|
+
content: content.replace(arrayPattern, `$1${quoted}, `),
|
|
8066
|
+
changed: true
|
|
8067
|
+
};
|
|
8068
|
+
const exportPattern = /(export default\s*\{)\n/;
|
|
8069
|
+
if (exportPattern.test(content)) return {
|
|
8070
|
+
content: content.replace(exportPattern, `$1\n ${field}: [${quoted}],\n`),
|
|
8071
|
+
changed: true
|
|
8072
|
+
};
|
|
8073
|
+
return {
|
|
8074
|
+
content,
|
|
8075
|
+
changed: false
|
|
8076
|
+
};
|
|
8077
|
+
}
|
|
8078
|
+
function ensureIgnoreInTs(content, ignoreBinaries, ignoreDependencies, needsHooksIgnore) {
|
|
7944
8079
|
let result = content;
|
|
7945
8080
|
let changed = false;
|
|
7946
8081
|
if (needsHooksIgnore && !result.includes(".claude/hooks")) {
|
|
@@ -7958,18 +8093,15 @@ function ensureIgnoreInTs(content, ignoreBinaries, needsHooksIgnore) {
|
|
|
7958
8093
|
}
|
|
7959
8094
|
const missingBinaries = missingIgnoreBinariesInTs(result, ignoreBinaries);
|
|
7960
8095
|
if (missingBinaries.length > 0) {
|
|
7961
|
-
const
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
changed = true;
|
|
7971
|
-
}
|
|
7972
|
-
}
|
|
8096
|
+
const inserted = insertIntoTsArray(result, "ignoreBinaries", missingBinaries);
|
|
8097
|
+
result = inserted.content;
|
|
8098
|
+
changed = changed || inserted.changed;
|
|
8099
|
+
}
|
|
8100
|
+
const missingDependencies = missingIgnoreDependenciesInTs(result, ignoreDependencies);
|
|
8101
|
+
if (missingDependencies.length > 0) {
|
|
8102
|
+
const inserted = insertIntoTsArray(result, "ignoreDependencies", missingDependencies);
|
|
8103
|
+
result = inserted.content;
|
|
8104
|
+
changed = changed || inserted.changed;
|
|
7973
8105
|
}
|
|
7974
8106
|
return changed ? result : void 0;
|
|
7975
8107
|
}
|
|
@@ -7985,6 +8117,7 @@ async function generateKnip(ctx) {
|
|
|
7985
8117
|
};
|
|
7986
8118
|
const isMonorepo = ctx.config.structure === "monorepo";
|
|
7987
8119
|
const ignoreBinaries = foreignOwnedBinaries(ctx);
|
|
8120
|
+
const ignoreDependencies = ignorableDependencies(ctx);
|
|
7988
8121
|
const buildConfig = isMonorepo ? buildKnipConfigMonorepo : buildKnipConfigSingle;
|
|
7989
8122
|
const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
7990
8123
|
if (existingPath) {
|
|
@@ -7995,7 +8128,7 @@ async function generateKnip(ctx) {
|
|
|
7995
8128
|
description: "Could not read existing knip config"
|
|
7996
8129
|
};
|
|
7997
8130
|
if (content.trim() === "") {
|
|
7998
|
-
const blank = isJsonConfig(existingPath) ? ensureIgnorePattern("{}", existingPath, ignoreBinaries) ?? "{}\n" : formattedTemplate(ctx, existingPath, buildConfig(ignoreBinaries));
|
|
8131
|
+
const blank = isJsonConfig(existingPath) ? ensureIgnorePattern("{}", existingPath, ignoreBinaries, ignoreDependencies) ?? "{}\n" : formattedTemplate(ctx, existingPath, buildConfig(ignoreBinaries, ignoreDependencies));
|
|
7999
8132
|
ctx.write(existingPath, blank);
|
|
8000
8133
|
return {
|
|
8001
8134
|
filePath: existingPath,
|
|
@@ -8003,13 +8136,13 @@ async function generateKnip(ctx) {
|
|
|
8003
8136
|
description: createdDescription(existingPath)
|
|
8004
8137
|
};
|
|
8005
8138
|
}
|
|
8006
|
-
const updated = ensureIgnorePattern(content, existingPath, ignoreBinaries);
|
|
8139
|
+
const updated = ensureIgnorePattern(content, existingPath, ignoreBinaries, ignoreDependencies);
|
|
8007
8140
|
if (updated) {
|
|
8008
8141
|
ctx.write(existingPath, updated);
|
|
8009
8142
|
return {
|
|
8010
8143
|
filePath: existingPath,
|
|
8011
8144
|
action: "updated",
|
|
8012
|
-
description: "Added \".claude/hooks/**\" and/or
|
|
8145
|
+
description: "Added \".claude/hooks/**\", foreign-owned ignoreBinaries, and/or ignored dependencies"
|
|
8013
8146
|
};
|
|
8014
8147
|
}
|
|
8015
8148
|
return {
|
|
@@ -8018,7 +8151,7 @@ async function generateKnip(ctx) {
|
|
|
8018
8151
|
description: existingPath === filePath ? "Already configured" : `Existing config found at ${existingPath}`
|
|
8019
8152
|
};
|
|
8020
8153
|
}
|
|
8021
|
-
ctx.write(filePath, formattedTemplate(ctx, filePath, buildConfig(ignoreBinaries)));
|
|
8154
|
+
ctx.write(filePath, formattedTemplate(ctx, filePath, buildConfig(ignoreBinaries, ignoreDependencies)));
|
|
8022
8155
|
return {
|
|
8023
8156
|
filePath,
|
|
8024
8157
|
action: "created",
|
|
@@ -8447,7 +8580,7 @@ async function generateReleaseIt(ctx) {
|
|
|
8447
8580
|
}
|
|
8448
8581
|
//#endregion
|
|
8449
8582
|
//#region src/generators/changesets.ts
|
|
8450
|
-
function buildConfig$2() {
|
|
8583
|
+
function buildConfig$2(defaultBranch) {
|
|
8451
8584
|
return {
|
|
8452
8585
|
$schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
8453
8586
|
changelog: "@changesets/cli/changelog",
|
|
@@ -8455,7 +8588,7 @@ function buildConfig$2() {
|
|
|
8455
8588
|
fixed: [],
|
|
8456
8589
|
linked: [],
|
|
8457
8590
|
access: "public",
|
|
8458
|
-
baseBranch:
|
|
8591
|
+
baseBranch: defaultBranch,
|
|
8459
8592
|
updateInternalDependencies: "patch",
|
|
8460
8593
|
ignore: []
|
|
8461
8594
|
};
|
|
@@ -8467,7 +8600,7 @@ async function generateChangesets(ctx) {
|
|
|
8467
8600
|
action: "skipped",
|
|
8468
8601
|
description: "Changesets not requested"
|
|
8469
8602
|
};
|
|
8470
|
-
const content = JSON.stringify(buildConfig$2(), null, 2) + "\n";
|
|
8603
|
+
const content = JSON.stringify(buildConfig$2(configuredDefaultBranch(ctx.read(".tooling.json"))), null, 2) + "\n";
|
|
8471
8604
|
const existing = ctx.read(filePath);
|
|
8472
8605
|
if (existing) return {
|
|
8473
8606
|
filePath,
|
|
@@ -9095,6 +9228,7 @@ const AGENT_RULES_MID = [
|
|
|
9095
9228
|
const TS_EXTENSIONLESS_RULE = "- Prefer extensionless imports; if an extension is required, use `.ts` over `.js`.";
|
|
9096
9229
|
const CHANGESETS_RULE = "- A user-facing change owes a changeset in `.changeset/`, written with the change: `patch` for a fix, `minor` for a feature, naming every package it affects. One file per smallest unit of work.";
|
|
9097
9230
|
const WORK_STATUS_RULE = "- Before starting or resuming work, run `bst work:status`.";
|
|
9231
|
+
const WORK_START_RULE = "- Before starting or resuming work, run `bst work:start <n>`.";
|
|
9098
9232
|
const FIND_COMMAND_RULE = "- Find a command with `bst --help`, its flags with `bst <command> --help`, before guessing.";
|
|
9099
9233
|
const FORGEJO_CLI_RULE_SUFFIX = " For Forgejo API work, run `bst-fj --help`.";
|
|
9100
9234
|
const AGENT_CLI_RULE_SUFFIX = " For the agent's runs and transcripts, run `bst-agent --help`.";
|
|
@@ -9115,20 +9249,22 @@ function declaresAgentCli(packageJson) {
|
|
|
9115
9249
|
function findCommandRule(packageJson) {
|
|
9116
9250
|
return FIND_COMMAND_RULE + (declaresForgejoCli(packageJson) ? FORGEJO_CLI_RULE_SUFFIX : "") + (declaresAgentCli(packageJson) ? AGENT_CLI_RULE_SUFFIX : "");
|
|
9117
9251
|
}
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9252
|
+
function agentRulesTail(agentWorked) {
|
|
9253
|
+
return [
|
|
9254
|
+
"- The bst-hook blocks a short list of dangerous commands (`.claude/hooks/bst-hook-guide.md` lists them); Claude Code's auto mode judges everything else.",
|
|
9255
|
+
"- Machine-reading a `bst` command? Pass `--json` after the command name (`bst <cmd> --json`, not `bst --json <cmd>`); capture `2>&1` and parse it as one JSON document, failures included as `{ok: false, error}`. `--verbose` breaks this.",
|
|
9256
|
+
agentWorked ? WORK_START_RULE : WORK_STATUS_RULE,
|
|
9257
|
+
FIND_COMMAND_RULE,
|
|
9258
|
+
"",
|
|
9259
|
+
"#### Git discipline",
|
|
9260
|
+
"",
|
|
9261
|
+
"**The branch you are on is the mode switch.** The bst-hook refuses any command that would move a protected branch, so on one you leave changes unstaged; on a feature branch you commit, push, and open the PR yourself. Pick the mode, announce it, and only ask when it's genuinely ambiguous.",
|
|
9262
|
+
"",
|
|
9263
|
+
"**Small change** -- a fix, a doc edit, a one-file tweak: work on the default branch and leave it unstaged. Don't commit; surface the commit message as text for the user to run.",
|
|
9264
|
+
"",
|
|
9265
|
+
"**Large effort** -- anything that warranted a written plan or spec (spec, then plan, then code, each approved before the next): start it in a worktree (see the Git guide), commit as you go, finish with `bst pr:create`. Push later commits with `bst pr:update`, which rewrites nothing unless given `--title`/`--body`. The one exception: a headless agent run (a spec, plan, execute, or review container) never starts a worktree -- it works and commits on the branch it was started on."
|
|
9266
|
+
];
|
|
9267
|
+
}
|
|
9132
9268
|
const COMMENTS_GUIDE_BODY = [
|
|
9133
9269
|
"# Comments",
|
|
9134
9270
|
"",
|
|
@@ -9136,7 +9272,7 @@ const COMMENTS_GUIDE_BODY = [
|
|
|
9136
9272
|
"",
|
|
9137
9273
|
"- Default to none. A comment earns its place by carrying what the code cannot -- a rejected alternative, the bug that forced this shape, an invariant enforced elsewhere -- and earns it one decision at a time, in a sentence or two each: what was rejected, and what it cost. Five decisions is five sentences, not five paragraphs. Recoverable from the code? Leave it unwritten.",
|
|
9138
9274
|
"- Present tense, about what is. No `now`, `previously`, `changed to`: diff narration is stale on the next edit, and the commit message carries it. No running ledgers or changelogs: when adjusting a threshold, constant, or baseline, state the active invariant and prune past deltas.",
|
|
9139
|
-
"- Never cite a lifecycle-managed doc, PR, or issue from code: no PR or issue numbers (`PR #123`, `closes #456`), no path under `docs/superpowers/` or `docs/design/`, none carrying a `retired/` or `completed/` segment, no `docs/plan-*.md`, and no bare decision or task ID (`D3`, `T2`). They move, retire, and renumber, nothing checks the reference, and the reader has no way to tell a dead one from a live one. State the constraint itself, or cite a maintained reference doc instead. A `docs/` path that is a glob or an elided example rather than a reference (`docs/*.md`) is vocabulary, not a citation, and is fine.",
|
|
9275
|
+
"- Never cite a lifecycle-managed doc, PR, or issue from code: no PR or issue numbers (`PR #123`, `closes #456`), no path under `docs/specs/`, `docs/plans/`, `docs/superpowers/` or `docs/design/`, none carrying a `retired/` or `completed/` segment, no `docs/plan-*.md`, and no bare decision or task ID (`D3`, `T2`). They move, retire, and renumber, nothing checks the reference, and the reader has no way to tell a dead one from a live one. State the constraint itself, or cite a maintained reference doc instead. A `docs/` path that is a glob or an elided example rather than a reference (`docs/*.md`) is vocabulary, not a citation, and is fine.",
|
|
9140
9276
|
"- Code that exists only to accept an older shape -- a file, field, flag, environment variable, label or comment format -- carries `// @compat <id> until <expiry>: <reason>` on the line above it. The expiry is a date (`YYYY-MM-DD`) when the old shape lives with consumers who upgrade on their own schedule; a version of the code's own package (`v1.4.0`) when its consumer upgrades with each release; or `forge-clean` for old data on the forge, which has neither. Remove the code and every test that carries the same id together.",
|
|
9141
9277
|
"- **The code is the fact.** When a comment disagrees with it, fix it in one line or delete it -- never grow one to argue the code is right."
|
|
9142
9278
|
].join("\n");
|
|
@@ -9167,7 +9303,11 @@ const GIT_GUIDE_BODY = [
|
|
|
9167
9303
|
"",
|
|
9168
9304
|
"## Claiming work by hand",
|
|
9169
9305
|
"",
|
|
9170
|
-
"If the bst agent drives this repo, claim an issue or pull request before you work it by hand, so the agent does not start a run beside you
|
|
9306
|
+
"If the bst agent drives this repo, claim an issue or pull request before you work it by hand, so the agent does not start a run beside you: run `bst work:start <n>`. It prints the same orientation `bst work:status` does and then puts `agent-flags/hand-authored` on the object. It claims only where the repo's default branch sets `agent.enabled: true`, and does nothing anywhere else. **Exit 1 means do not start**: a run is live on the pair, or the agent holds it -- the output names which. Exit 0 means the pair is yours, whether the command wrote the flag or found it there already.",
|
|
9307
|
+
"",
|
|
9308
|
+
"A pull request you open yourself that closes no claimed issue needs its own `bst work:start <n>` once it exists -- the flag on either half of a pair holds both, but there is no pair until something closes something.",
|
|
9309
|
+
"",
|
|
9310
|
+
"Hand the work back with the control panel's **Release to the agent**, or by removing `agent-flags/hand-authored` from both halves. Removing it does not queue anything: the agent picks the object up on its next cycle only if a label or a comment asks it to.",
|
|
9171
9311
|
"",
|
|
9172
9312
|
"## Issue linking",
|
|
9173
9313
|
"",
|
|
@@ -9198,7 +9338,7 @@ const PLAN_LIFECYCLE_GUIDE_BODY = [
|
|
|
9198
9338
|
"",
|
|
9199
9339
|
"A plan's **directory** is the source of truth for whether it shipped -- not its `Status:` line, not its checkboxes. Read the source it names.",
|
|
9200
9340
|
"",
|
|
9201
|
-
"`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc
|
|
9341
|
+
"`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc lives under `docs/plans/` (older ones under `docs/superpowers/plans/`), even one drafted while running a plan through subagents.",
|
|
9202
9342
|
"",
|
|
9203
9343
|
"Retiring the plan is the last task of the plan, done in the PR that delivers the work: \"in the tree\" means committed on this branch, not merged to a default branch. Run `bst docs:retire <plan.md> --status <verb> --shipped \"<text>\"` -- it moves the doc, relinks what the move breaks, de-links paths pointing outside `docs/`, and writes the dated terminal `Status:` line, the historical banner, and the \"Shipped:\" paragraph. You supply the judgement: the honest verb -- `completed` / `superseded` / `abandoned` / `rejected` -- and a truthful `--shipped` naming the delivering PR; never fabricate it for uncommitted code or claim a merge that has not happened. A plan that lands one phase of a larger spec retires the plan and leaves the spec open.",
|
|
9204
9344
|
"",
|
|
@@ -9215,11 +9355,11 @@ const PLANNING_GUIDE_BODY = [
|
|
|
9215
9355
|
"",
|
|
9216
9356
|
"## The spec",
|
|
9217
9357
|
"",
|
|
9218
|
-
"Say why and what, not how: the problem, who it is for, the constraints, what done looks like, and the alternatives you rejected, each with its reason. Work with several independent parts gets one spec per part. Save it as `docs/
|
|
9358
|
+
"Say why and what, not how: the problem, who it is for, the constraints, what done looks like, and the alternatives you rejected, each with its reason. Work with several independent parts gets one spec per part. Save it as `docs/specs/<YYYY-MM-DD>-<topic>-design.md`.",
|
|
9219
9359
|
"",
|
|
9220
9360
|
"## The plan",
|
|
9221
9361
|
"",
|
|
9222
|
-
"Write for an executor who has not seen this repo or the spec. They write idiomatic code once they know the exact interface and the exact test, and make a reasonable choice wherever the plan leaves one open; what they cannot know is what you decided. A plan records those decisions -- it is not a transcript of the code. Save it under `docs/
|
|
9362
|
+
"Write for an executor who has not seen this repo or the spec. They write idiomatic code once they know the exact interface and the exact test, and make a reasonable choice wherever the plan leaves one open; what they cannot know is what you decided. A plan records those decisions -- it is not a transcript of the code. Save it under `docs/plans/`.",
|
|
9223
9363
|
"",
|
|
9224
9364
|
"- Check the plan's premises against the tree before building on them. \"Today X is broken\" is a claim: read the code that handles X, not just the line that throws.",
|
|
9225
9365
|
"- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `bst plan:brief` can find it. A checkbox's first sentence is the task's name on the control panel: end it with a period inside one short line, and put the files, commands, and checks in the sentences after it.",
|
|
@@ -9288,18 +9428,18 @@ function webUiGuideBody() {
|
|
|
9288
9428
|
"@types/node": "24.19.1",
|
|
9289
9429
|
"@vitest/coverage-v8": "5.0.3",
|
|
9290
9430
|
"commit-and-tag-version": "13.2.1",
|
|
9291
|
-
"knip": "6.
|
|
9292
|
-
"lefthook": "2.1
|
|
9293
|
-
"modern-web-guidance": "0.0.
|
|
9294
|
-
"oxfmt": "0.
|
|
9295
|
-
"oxlint": "1.
|
|
9431
|
+
"knip": "6.41.0",
|
|
9432
|
+
"lefthook": "2.2.1",
|
|
9433
|
+
"modern-web-guidance": "0.0.193",
|
|
9434
|
+
"oxfmt": "0.72.0",
|
|
9435
|
+
"oxlint": "1.87.0",
|
|
9296
9436
|
"oxlint-tsgolint": "7.0.2003",
|
|
9297
9437
|
"prettier": "3.9.9",
|
|
9298
|
-
"release-it": "21.1.
|
|
9438
|
+
"release-it": "21.1.1",
|
|
9299
9439
|
"tsdown": "0.23.0",
|
|
9300
9440
|
"typescript": "6.0.3",
|
|
9301
9441
|
"vitest": "5.0.3",
|
|
9302
|
-
"pnpm": "11.28.
|
|
9442
|
+
"pnpm": "11.28.5"
|
|
9303
9443
|
}["modern-web-guidance"]}`;
|
|
9304
9444
|
return [
|
|
9305
9445
|
"# Web UI",
|
|
@@ -9548,13 +9688,13 @@ function guideIndex(guides) {
|
|
|
9548
9688
|
for (const guide of guides.filter((g) => g.enabled)) lines.push(`- Before you ${guide.trigger}: \`${GUIDES_DIR}/${guide.name}\`.`);
|
|
9549
9689
|
return lines.join("\n");
|
|
9550
9690
|
}
|
|
9551
|
-
function agentRulesBody(config, guides, packageJson) {
|
|
9691
|
+
function agentRulesBody(config, guides, packageJson, agentWorked) {
|
|
9552
9692
|
const lines = [...AGENT_RULES_HEAD];
|
|
9553
9693
|
if (config.stack === "typescript") lines.push(TS_TYPECAST_RULE);
|
|
9554
9694
|
lines.push(...AGENT_RULES_MID);
|
|
9555
9695
|
if (config.stack === "typescript") lines.push(TS_EXTENSIONLESS_RULE);
|
|
9556
9696
|
if (config.releaseStrategy === "changesets") lines.push(CHANGESETS_RULE);
|
|
9557
|
-
const tail =
|
|
9697
|
+
const tail = agentRulesTail(agentWorked).map((line) => line === FIND_COMMAND_RULE ? findCommandRule(packageJson) : line);
|
|
9558
9698
|
lines.push(...tail, "", guideIndex(guides));
|
|
9559
9699
|
return lines.join("\n");
|
|
9560
9700
|
}
|
|
@@ -9618,7 +9758,7 @@ function syncClaudeMdFile(ctx, guides) {
|
|
|
9618
9758
|
name: "agent-rules",
|
|
9619
9759
|
startMarker: "<!-- @tooling:agent-rules -->",
|
|
9620
9760
|
endMarker: "<!-- @tooling:endagent-rules -->",
|
|
9621
|
-
body: agentRulesBody(ctx.config, guides, ctx.packageJson),
|
|
9761
|
+
body: agentRulesBody(ctx.config, guides, ctx.packageJson, agentEnabled(ctx)),
|
|
9622
9762
|
enabled: true
|
|
9623
9763
|
}, ...RETIRED_BLOCK_NAMES.map((name) => ({
|
|
9624
9764
|
name,
|
|
@@ -9859,7 +9999,7 @@ function reportFailures(failed, command) {
|
|
|
9859
9999
|
function generateMigratePrompt(results, config, detected) {
|
|
9860
10000
|
const sections = [];
|
|
9861
10001
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
9862
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
10002
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.91.0 repo:sync\` on ${timestamp}_\n`);
|
|
9863
10003
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
9864
10004
|
if (changed.length > 0) {
|
|
9865
10005
|
const items = changed.map((r) => {
|
|
@@ -9987,7 +10127,7 @@ function logDetectionSummary(ctx) {
|
|
|
9987
10127
|
log$1.info(`Release assets convention detected in ${label} -- emit build artifacts to ${CONVENTION_OUTPUT_DIR}/ (gitignored).`);
|
|
9988
10128
|
}
|
|
9989
10129
|
}
|
|
9990
|
-
async function
|
|
10130
|
+
async function runInitReport(config, options = {}) {
|
|
9991
10131
|
const detected = detectProject(config.targetDir);
|
|
9992
10132
|
const { ctx, archivedFiles, sawCrlf } = createContext(config, options.confirmOverwrite ?? (async (relativePath) => {
|
|
9993
10133
|
const result = await select({
|
|
@@ -10024,7 +10164,10 @@ async function runInit(config, options = {}) {
|
|
|
10024
10164
|
}
|
|
10025
10165
|
if (!hasChanges && options.noPrompt) {
|
|
10026
10166
|
if (failed.length === 0) log$1.success("Repository is up to date.");
|
|
10027
|
-
return
|
|
10167
|
+
return {
|
|
10168
|
+
results,
|
|
10169
|
+
install: "not-run"
|
|
10170
|
+
};
|
|
10028
10171
|
}
|
|
10029
10172
|
if (results.some((r) => r.action === "archived" && r.filePath.startsWith(".husky/"))) try {
|
|
10030
10173
|
execSync("git config --unset core.hooksPath", {
|
|
@@ -10043,16 +10186,19 @@ async function runInit(config, options = {}) {
|
|
|
10043
10186
|
if (!options.noPrompt) log$1.info(`Migration prompt written to ${promptPath}`);
|
|
10044
10187
|
const packageJsonChanged = results.some((r) => r.filePath === "package.json" && (r.action === "created" || r.action === "updated"));
|
|
10045
10188
|
const hasLockfile = ctx.exists("pnpm-lock.yaml");
|
|
10189
|
+
let install = "not-run";
|
|
10046
10190
|
if (packageJsonChanged && hasLockfile) {
|
|
10047
10191
|
log$1.info("package.json changed -- running pnpm install...");
|
|
10048
10192
|
try {
|
|
10049
10193
|
execSync("pnpm install", {
|
|
10050
10194
|
cwd: config.targetDir,
|
|
10051
|
-
stdio: "inherit",
|
|
10195
|
+
stdio: options.installOutput === "capture" ? "ignore" : "inherit",
|
|
10052
10196
|
timeout: 12e4
|
|
10053
10197
|
});
|
|
10198
|
+
install = "ran";
|
|
10054
10199
|
} catch (_error) {
|
|
10055
10200
|
log$1.warn("pnpm install failed -- run it manually");
|
|
10201
|
+
install = "failed";
|
|
10056
10202
|
}
|
|
10057
10203
|
}
|
|
10058
10204
|
if (hasChanges && ctx.exists("package.json")) try {
|
|
@@ -10067,7 +10213,13 @@ async function runInit(config, options = {}) {
|
|
|
10067
10213
|
"Run: pnpm check",
|
|
10068
10214
|
...!options.noPrompt ? ["Run: claude \"Execute the migration steps in .tooling-migrate.md\""] : []
|
|
10069
10215
|
].map((step, i) => `${i + 1}. ${step}`).join("\n"), "Next steps");
|
|
10070
|
-
return
|
|
10216
|
+
return {
|
|
10217
|
+
results,
|
|
10218
|
+
install
|
|
10219
|
+
};
|
|
10220
|
+
}
|
|
10221
|
+
async function runInit(config, options = {}) {
|
|
10222
|
+
return (await runInitReport(config, options)).results;
|
|
10071
10223
|
}
|
|
10072
10224
|
//#endregion
|
|
10073
10225
|
//#region src/docker-check/compose-lint.ts
|
|
@@ -10806,34 +10958,6 @@ function repoPrefix(executor, cwd) {
|
|
|
10806
10958
|
function ceilingFileNamesAt(prefix) {
|
|
10807
10959
|
return HEALTH_CEILING_FILES.map((name) => `${prefix}${name}`);
|
|
10808
10960
|
}
|
|
10809
|
-
function processCiEventSource() {
|
|
10810
|
-
return {
|
|
10811
|
-
env: process.env,
|
|
10812
|
-
readFile: (absolutePath) => {
|
|
10813
|
-
try {
|
|
10814
|
-
return readFileSync(absolutePath, "utf-8");
|
|
10815
|
-
} catch (_error) {
|
|
10816
|
-
return;
|
|
10817
|
-
}
|
|
10818
|
-
}
|
|
10819
|
-
};
|
|
10820
|
-
}
|
|
10821
|
-
const CiEventSchema = z.object({ repository: z.object({ default_branch: z.string().min(1) }) });
|
|
10822
|
-
function ciDefaultBranch(source) {
|
|
10823
|
-
if (source === void 0 || source.env["GITHUB_ACTIONS"] !== "true") return void 0;
|
|
10824
|
-
const eventPath = source.env["GITHUB_EVENT_PATH"];
|
|
10825
|
-
if (eventPath === void 0 || eventPath === "") return void 0;
|
|
10826
|
-
const raw = source.readFile(eventPath);
|
|
10827
|
-
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
10828
|
-
let json;
|
|
10829
|
-
try {
|
|
10830
|
-
json = JSON.parse(raw);
|
|
10831
|
-
} catch (_error) {
|
|
10832
|
-
return;
|
|
10833
|
-
}
|
|
10834
|
-
const result = CiEventSchema.safeParse(json);
|
|
10835
|
-
return result.success ? result.data.repository.default_branch : void 0;
|
|
10836
|
-
}
|
|
10837
10961
|
const DefaultBranchSchema = z.object({ defaultBranch: z.string().optional() });
|
|
10838
10962
|
function parseDefaultBranchOverride(raw) {
|
|
10839
10963
|
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
@@ -10880,7 +11004,7 @@ function originHeadBranch(git, cwd) {
|
|
|
10880
11004
|
return name === "" ? void 0 : name;
|
|
10881
11005
|
}
|
|
10882
11006
|
function unconfirmedDefaultWarning(branch, namedByTree) {
|
|
10883
|
-
return `health: could not confirm the default branch, so the reference was read against origin/${branch}, while .tooling.json#defaultBranch names ${namedByTree}.
|
|
11007
|
+
return `health: could not confirm the default branch, so the reference was read against origin/${branch}, while .tooling.json#defaultBranch names ${namedByTree}. If ${namedByTree} is the repository's default branch, record it in this clone with \`git remote set-head origin --auto\`, or run in GitHub or Forgejo Actions, where the event payload names it.`;
|
|
10884
11008
|
}
|
|
10885
11009
|
function gitRunner(executor) {
|
|
10886
11010
|
return (args, cwd) => {
|
|
@@ -11962,10 +12086,10 @@ function runComposeLintGate(targetDir, config, memo) {
|
|
|
11962
12086
|
for (const warning of findDockerRebuildRisks(targetDir, config, memo)) log$1.warn(warning);
|
|
11963
12087
|
return issues.length;
|
|
11964
12088
|
}
|
|
11965
|
-
function runCheckOnlyImageGate(config, targetDir, memo) {
|
|
12089
|
+
function runCheckOnlyImageGate(config, targetDir, memo, phase) {
|
|
11966
12090
|
if (!config.publishDocker) return [];
|
|
11967
12091
|
const images = [];
|
|
11968
|
-
for (const risk of findCheckOnlyPublishRisks(targetDir,
|
|
12092
|
+
for (const risk of findCheckOnlyPublishRisks(targetDir, phase, memo)) {
|
|
11969
12093
|
log$1.warn(`Image "${risk.imageName}" is declared only in ${risk.declaredIn}, the check overlay, so a later tooling release stops publishing it with docker:publish by default. Keep it: set publish to true for "${risk.imageName}" under .tooling.json#docker. Settle it: set publish to false there, or move the service into a base compose file.`);
|
|
11970
12094
|
images.push(risk.imageName);
|
|
11971
12095
|
}
|
|
@@ -11979,6 +12103,18 @@ function runWorkflowAptGate(targetDir) {
|
|
|
11979
12103
|
}
|
|
11980
12104
|
return steps;
|
|
11981
12105
|
}
|
|
12106
|
+
function findInertAgentKeys(saved) {
|
|
12107
|
+
const agent = saved?.agent;
|
|
12108
|
+
if (!agent) return [];
|
|
12109
|
+
if (agent.enabled === true) return [];
|
|
12110
|
+
return Object.keys(agent).filter((key) => key !== "enabled").toSorted();
|
|
12111
|
+
}
|
|
12112
|
+
function runInertAgentSettingsGate(saved) {
|
|
12113
|
+
const keys = findInertAgentKeys(saved);
|
|
12114
|
+
if (keys.length === 0) return [];
|
|
12115
|
+
log$1.warn(`.tooling.json sets ${keys.map((key) => `agent.${key}`).join(", ")}, but agent.enabled is not true, so the agent ignores them. Run \`bst setup:agent\` to enable the agent, or remove those keys.`);
|
|
12116
|
+
return keys;
|
|
12117
|
+
}
|
|
11982
12118
|
function runPublishConfigGate(config, targetDir) {
|
|
11983
12119
|
if (!config.publishNpm) return 0;
|
|
11984
12120
|
const issues = lintPublishConfig(targetDir, config.structure);
|
|
@@ -12024,6 +12160,18 @@ function warnStaleChecksAdd(config) {
|
|
|
12024
12160
|
if (stale.length === 0) return;
|
|
12025
12161
|
log$1.warn(`.tooling.json#checks.add names built-in step(s) checks:run already runs: ${stale.join(", ")}. The entries are deduplicated at run time and do nothing -- remove them.`);
|
|
12026
12162
|
}
|
|
12163
|
+
function warnUnconfiguredDefaultBranch(targetDir, saved, git = defaultGitRunner) {
|
|
12164
|
+
if (saved?.defaultBranch !== void 0) return;
|
|
12165
|
+
const head = git([
|
|
12166
|
+
"symbolic-ref",
|
|
12167
|
+
"--short",
|
|
12168
|
+
"refs/remotes/origin/HEAD"
|
|
12169
|
+
], targetDir);
|
|
12170
|
+
if (head === void 0) return;
|
|
12171
|
+
const name = head.startsWith("origin/") ? head.slice(7) : head;
|
|
12172
|
+
if (name === "" || name === "main") return;
|
|
12173
|
+
log$1.warn(`origin/HEAD names "${name}", but .tooling.json sets no defaultBranch, so the generated CI workflow triggers on "${DEFAULT_BRANCH}" and gates its release steps there. Set .tooling.json#defaultBranch to "${name}" and run bst repo:sync.`);
|
|
12174
|
+
}
|
|
12027
12175
|
function warnCoveredChecksAdd(config, targetDir) {
|
|
12028
12176
|
const covered = coveredChecksAddEntries(config, readPackageJson(targetDir)?.scripts ?? {});
|
|
12029
12177
|
for (const entry of covered) log$1.warn(`.tooling.json#checks.add entry "${entry.name}" is already run by the "${entry.coveredBy}" step's script ("${entry.fragment}"), so every pipeline run executes it twice. Remove the add entry, or drop the inline invocation from the "${entry.coveredBy}" script.`);
|
|
@@ -12143,26 +12291,14 @@ function checkDeclaredToolsActive(targetDir, declared) {
|
|
|
12143
12291
|
}
|
|
12144
12292
|
function runToolchainGate(targetDir) {
|
|
12145
12293
|
const miseTomlPath = path.join(targetDir, "mise.toml");
|
|
12146
|
-
if (!existsSync(miseTomlPath)) return
|
|
12147
|
-
failures: 0,
|
|
12148
|
-
warned: false
|
|
12149
|
-
};
|
|
12294
|
+
if (!existsSync(miseTomlPath)) return 0;
|
|
12150
12295
|
if (miseIsAbsent(targetDir)) {
|
|
12151
|
-
log$1.
|
|
12152
|
-
return
|
|
12153
|
-
failures: 0,
|
|
12154
|
-
warned: true
|
|
12155
|
-
};
|
|
12296
|
+
log$1.error(`mise.toml declares this repo's toolchain, but mise is not on PATH. Install it (${MISE_INSTALL_URL}), then add \`eval "$(mise activate bash)"\` to ~/.bashrc for interactive shells and \`eval "$(mise activate bash --shims)"\` to ~/.profile, so editors, git hooks, and AI sessions get the same tools. If this repo's mise.toml has an [env] block, also add the directory your checkouts live in to \`trusted_config_paths\` in ~/.config/mise/config.toml.`);
|
|
12297
|
+
return 1;
|
|
12156
12298
|
}
|
|
12157
12299
|
const declared = readDeclaredTools(miseTomlPath);
|
|
12158
|
-
if (!declared) return
|
|
12159
|
-
|
|
12160
|
-
warned: false
|
|
12161
|
-
};
|
|
12162
|
-
return {
|
|
12163
|
-
failures: checkDeclaredToolsActive(targetDir, declared),
|
|
12164
|
-
warned: false
|
|
12165
|
-
};
|
|
12300
|
+
if (!declared) return 0;
|
|
12301
|
+
return checkDeclaredToolsActive(targetDir, declared);
|
|
12166
12302
|
}
|
|
12167
12303
|
//#endregion
|
|
12168
12304
|
//#region src/commands/repo-sync.ts
|
|
@@ -12204,7 +12340,7 @@ const syncCommand = defineCommand({
|
|
|
12204
12340
|
},
|
|
12205
12341
|
json: {
|
|
12206
12342
|
type: "boolean",
|
|
12207
|
-
description: "Emit one JSON document: status (up-to-date | drift | workflow-drift | failed), actionable, failed,
|
|
12343
|
+
description: "Emit one JSON document: status (up-to-date | drift | workflow-drift | failed), actionable, failed, checkOnlyImages, aptSteps, inertAgentKeys, warnCount, exitCode. --check only: the write mode prompts and inherits child streams."
|
|
12208
12344
|
}
|
|
12209
12345
|
},
|
|
12210
12346
|
async run({ args }) {
|
|
@@ -12242,47 +12378,71 @@ const syncCommand = defineCommand({
|
|
|
12242
12378
|
})).length > 0) process.exitCode = 1;
|
|
12243
12379
|
}
|
|
12244
12380
|
});
|
|
12245
|
-
async function
|
|
12381
|
+
async function runUpdateReport(targetDir, flags = {}, options = {}) {
|
|
12246
12382
|
const dockerDiscovery = createDockerDiscoveryMemo();
|
|
12247
12383
|
const saved = loadToolingConfigStrict(targetDir);
|
|
12248
12384
|
if (!saved) {
|
|
12249
12385
|
log$1.error("No .tooling.json found. Run `bst repo:sync` first to initialize the project.");
|
|
12250
12386
|
process.exitCode = 1;
|
|
12251
|
-
return
|
|
12387
|
+
return {
|
|
12388
|
+
status: "stopped",
|
|
12389
|
+
results: [],
|
|
12390
|
+
install: "not-run"
|
|
12391
|
+
};
|
|
12252
12392
|
}
|
|
12253
12393
|
warnStaleChecksAdd(saved);
|
|
12394
|
+
warnUnconfiguredDefaultBranch(targetDir, saved);
|
|
12254
12395
|
warnCoveredChecksAdd(saved, targetDir);
|
|
12396
|
+
runInertAgentSettingsGate(saved);
|
|
12255
12397
|
if (runComposeLintGate(targetDir, saved, dockerDiscovery) > 0) {
|
|
12256
12398
|
process.exitCode = 1;
|
|
12257
|
-
return
|
|
12399
|
+
return {
|
|
12400
|
+
status: "stopped",
|
|
12401
|
+
results: [],
|
|
12402
|
+
install: "not-run"
|
|
12403
|
+
};
|
|
12258
12404
|
}
|
|
12259
12405
|
const config = mergeWithSavedConfig(buildDefaultConfig(targetDir, flags), saved);
|
|
12260
12406
|
runReleaseTargetGate(config, targetDir);
|
|
12261
12407
|
runVersionStampGate(saved, targetDir, config.releaseStrategy);
|
|
12262
12408
|
if (runPublishConfigGate(config, targetDir) > 0) {
|
|
12263
12409
|
process.exitCode = 1;
|
|
12264
|
-
return
|
|
12410
|
+
return {
|
|
12411
|
+
status: "stopped",
|
|
12412
|
+
results: [],
|
|
12413
|
+
install: "not-run"
|
|
12414
|
+
};
|
|
12265
12415
|
}
|
|
12266
|
-
return
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12416
|
+
return {
|
|
12417
|
+
status: "ran",
|
|
12418
|
+
...await runInitReport(config, {
|
|
12419
|
+
noPrompt: true,
|
|
12420
|
+
confirmOverwrite: async () => "overwrite",
|
|
12421
|
+
dockerDiscovery,
|
|
12422
|
+
installOutput: options.installOutput
|
|
12423
|
+
})
|
|
12424
|
+
};
|
|
12425
|
+
}
|
|
12426
|
+
async function runUpdate(targetDir, flags = {}) {
|
|
12427
|
+
return (await runUpdateReport(targetDir, flags)).results;
|
|
12271
12428
|
}
|
|
12272
|
-
function gateFailure(
|
|
12429
|
+
function gateFailure() {
|
|
12273
12430
|
return {
|
|
12274
12431
|
status: "failed",
|
|
12275
12432
|
actionable: [],
|
|
12276
12433
|
failed: [],
|
|
12277
|
-
toolchain: { warned: toolchainWarned },
|
|
12278
12434
|
checkOnlyImages: [],
|
|
12279
12435
|
aptSteps: [],
|
|
12436
|
+
inertAgentKeys: [],
|
|
12280
12437
|
warnCount: 0,
|
|
12281
12438
|
exitCode: 1
|
|
12282
12439
|
};
|
|
12283
12440
|
}
|
|
12284
|
-
function
|
|
12285
|
-
|
|
12441
|
+
function advisoryWarnCount(advisories) {
|
|
12442
|
+
return advisories.checkOnlyImages.length + advisories.aptSteps.length + (advisories.inertAgentKeys.length > 0 ? 1 : 0);
|
|
12443
|
+
}
|
|
12444
|
+
function upToDateReport(advisories) {
|
|
12445
|
+
const warnCount = advisoryWarnCount(advisories);
|
|
12286
12446
|
if (warnCount > 0) {
|
|
12287
12447
|
if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
|
|
12288
12448
|
} else log$1.success("Repository is up to date.");
|
|
@@ -12290,9 +12450,9 @@ function upToDateReport(toolchain, checkOnlyImages, aptSteps) {
|
|
|
12290
12450
|
status: "up-to-date",
|
|
12291
12451
|
actionable: [],
|
|
12292
12452
|
failed: [],
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12453
|
+
checkOnlyImages: advisories.checkOnlyImages,
|
|
12454
|
+
aptSteps: advisories.aptSteps,
|
|
12455
|
+
inertAgentKeys: advisories.inertAgentKeys,
|
|
12296
12456
|
warnCount,
|
|
12297
12457
|
exitCode: 0
|
|
12298
12458
|
};
|
|
@@ -12300,6 +12460,7 @@ function upToDateReport(toolchain, checkOnlyImages, aptSteps) {
|
|
|
12300
12460
|
async function runCheckReport(targetDir) {
|
|
12301
12461
|
const dockerDiscovery = createDockerDiscoveryMemo();
|
|
12302
12462
|
const saved = loadToolingConfigStrict(targetDir);
|
|
12463
|
+
warnUnconfiguredDefaultBranch(targetDir, saved);
|
|
12303
12464
|
if (saved) {
|
|
12304
12465
|
warnStaleChecksAdd(saved);
|
|
12305
12466
|
warnCoveredChecksAdd(saved, targetDir);
|
|
@@ -12310,44 +12471,42 @@ async function runCheckReport(targetDir) {
|
|
|
12310
12471
|
if (runReleaseTargetGate(config, targetDir)) return gateFailure();
|
|
12311
12472
|
if (runVersionStampGate(saved, targetDir, config.releaseStrategy) > 0) return gateFailure();
|
|
12312
12473
|
if (runPublishConfigGate(config, targetDir) > 0) return gateFailure();
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12474
|
+
if (runToolchainGate(targetDir) > 0) return gateFailure();
|
|
12475
|
+
const advisories = {
|
|
12476
|
+
checkOnlyImages: runCheckOnlyImageGate(config, targetDir, dockerDiscovery),
|
|
12477
|
+
aptSteps: runWorkflowAptGate(targetDir),
|
|
12478
|
+
inertAgentKeys: runInertAgentSettingsGate(saved)
|
|
12317
12479
|
};
|
|
12318
|
-
const checkOnlyImages = runCheckOnlyImageGate(config, targetDir, dockerDiscovery);
|
|
12319
|
-
const aptSteps = runWorkflowAptGate(targetDir);
|
|
12320
12480
|
const { ctx, pendingWrites } = createDryRunContext(config, dockerDiscovery);
|
|
12321
12481
|
logDetectionSummary(ctx);
|
|
12322
12482
|
const results = await runGenerators(ctx);
|
|
12323
12483
|
const failed = failedResults(results);
|
|
12324
12484
|
reportFailures(failed, "repo:sync");
|
|
12325
12485
|
const actionable = actionableResults(results, pendingWrites, targetDir);
|
|
12326
|
-
const
|
|
12327
|
-
|
|
12328
|
-
if (actionable.length === 0 && failed.length === 0) return upToDateReport(toolchainState, checkOnlyImages, aptSteps);
|
|
12486
|
+
const advisoryCount = advisoryWarnCount(advisories);
|
|
12487
|
+
if (actionable.length === 0 && failed.length === 0) return upToDateReport(advisories);
|
|
12329
12488
|
if (actionable.length === 0) return {
|
|
12330
12489
|
status: "failed",
|
|
12331
12490
|
actionable: [],
|
|
12332
12491
|
failed,
|
|
12333
|
-
toolchain: toolchainState,
|
|
12334
12492
|
checkOnlyImages: [],
|
|
12335
12493
|
aptSteps: [],
|
|
12494
|
+
inertAgentKeys: [],
|
|
12336
12495
|
warnCount: 0,
|
|
12337
12496
|
exitCode: 1
|
|
12338
12497
|
};
|
|
12339
12498
|
log$1.warn(`${actionable.length} file(s) would be changed by repo:sync`);
|
|
12340
12499
|
for (const r of actionable) logActionableChange(r, targetDir, pendingWrites);
|
|
12341
12500
|
if (failed.length === 0 && actionable.every((r) => isWorkflowDirPath(r.filePath))) {
|
|
12342
|
-
const warnCount = actionable.length +
|
|
12501
|
+
const warnCount = actionable.length + advisoryCount;
|
|
12343
12502
|
if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
|
|
12344
12503
|
return {
|
|
12345
12504
|
status: "workflow-drift",
|
|
12346
12505
|
actionable,
|
|
12347
12506
|
failed: [],
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12507
|
+
checkOnlyImages: advisories.checkOnlyImages,
|
|
12508
|
+
aptSteps: advisories.aptSteps,
|
|
12509
|
+
inertAgentKeys: advisories.inertAgentKeys,
|
|
12351
12510
|
warnCount,
|
|
12352
12511
|
exitCode: 0
|
|
12353
12512
|
};
|
|
@@ -12356,9 +12515,9 @@ async function runCheckReport(targetDir) {
|
|
|
12356
12515
|
status: "drift",
|
|
12357
12516
|
actionable,
|
|
12358
12517
|
failed,
|
|
12359
|
-
toolchain: toolchainState,
|
|
12360
12518
|
checkOnlyImages: [],
|
|
12361
12519
|
aptSteps: [],
|
|
12520
|
+
inertAgentKeys: [],
|
|
12362
12521
|
warnCount: 0,
|
|
12363
12522
|
exitCode: 1
|
|
12364
12523
|
};
|
|
@@ -12715,8 +12874,9 @@ function childOutput(result) {
|
|
|
12715
12874
|
}
|
|
12716
12875
|
//#endregion
|
|
12717
12876
|
//#region src/release/publish/version.ts
|
|
12718
|
-
|
|
12719
|
-
|
|
12877
|
+
function defaultBranchRef(config) {
|
|
12878
|
+
return `refs/heads/${config.defaultBranch}`;
|
|
12879
|
+
}
|
|
12720
12880
|
function readRemoteRef(executor, config, auth, ref) {
|
|
12721
12881
|
const result = executor.exec("git", [
|
|
12722
12882
|
...auth.args,
|
|
@@ -12728,13 +12888,13 @@ function readRemoteRef(executor, config, auth, ref) {
|
|
|
12728
12888
|
return result.stdout.split("\n").find((entry) => entry.trim().endsWith(`\t${ref}`))?.split(" ")[0]?.trim() ?? "";
|
|
12729
12889
|
}
|
|
12730
12890
|
function isSuperseded(executor, config, auth, baseSha) {
|
|
12731
|
-
const
|
|
12732
|
-
if (
|
|
12733
|
-
debug(config,
|
|
12891
|
+
const remoteDefault = readRemoteRef(executor, config, auth, defaultBranchRef(config));
|
|
12892
|
+
if (remoteDefault === null || remoteDefault === "" || baseSha === "") {
|
|
12893
|
+
debug(config, `Supersession check: ${config.defaultBranch} tip or base commit unreadable -- proceeding`);
|
|
12734
12894
|
return false;
|
|
12735
12895
|
}
|
|
12736
|
-
if (
|
|
12737
|
-
log$1.warn(`Superseded release run: this job versioned ${baseSha}, but
|
|
12896
|
+
if (remoteDefault === baseSha) return false;
|
|
12897
|
+
log$1.warn(`Superseded release run: this job versioned ${baseSha}, but ${config.defaultBranch} has advanced to ${remoteDefault}. A newer run is versioning a superset of these changesets -- skipping to avoid overwriting its version commit on ${versionPrBranch(config.defaultBranch)}.`);
|
|
12738
12898
|
return true;
|
|
12739
12899
|
}
|
|
12740
12900
|
const DATELESS_VERSION_RE = /^## (\d+\.\d+\.\d+(?:-[^\s)]+)?)$/gm;
|
|
@@ -12821,12 +12981,14 @@ function buildPrContent(executor, cwd, packagesBefore) {
|
|
|
12821
12981
|
async function runVersionMode(executor, config) {
|
|
12822
12982
|
log$1.info("Changesets detected -- versioning packages");
|
|
12823
12983
|
const auth = buildGitAuth(config);
|
|
12984
|
+
const branch = versionPrBranch(config.defaultBranch);
|
|
12985
|
+
const branchRef = `refs/heads/${branch}`;
|
|
12824
12986
|
const baseShaResult = executor.exec("git", ["rev-parse", "HEAD"], { cwd: config.cwd });
|
|
12825
12987
|
const baseSha = baseShaResult.exitCode === 0 ? baseShaResult.stdout.trim() : "";
|
|
12826
|
-
const leaseTip = config.dryRun ? null : readRemoteRef(executor, config, auth,
|
|
12827
|
-
if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${
|
|
12988
|
+
const leaseTip = config.dryRun ? null : readRemoteRef(executor, config, auth, branchRef);
|
|
12989
|
+
if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${branch} from the remote, so this run cannot take a push lease. Refusing to force-push without one -- an unleased push is what allows a concurrent run's version commit to be silently discarded.`);
|
|
12828
12990
|
debug(config, `Base commit: ${baseSha || "(unknown)"}`);
|
|
12829
|
-
debug(config, `Lease tip for ${
|
|
12991
|
+
debug(config, `Lease tip for ${branch}: ${leaseTip === null ? "(dry-run)" : leaseTip || "(absent)"}`);
|
|
12830
12992
|
const packagesBefore = executor.listWorkspacePackages(config.cwd);
|
|
12831
12993
|
debug(config, `Packages before versioning: ${packagesBefore.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "(none)"}`);
|
|
12832
12994
|
const changesetConfigPath = path.join(config.cwd, ".changeset", "config.json");
|
|
@@ -12888,28 +13050,28 @@ async function runVersionMode(executor, config) {
|
|
|
12888
13050
|
pr: "none"
|
|
12889
13051
|
};
|
|
12890
13052
|
if (leaseTip === null) throw new UnexpectedError("Push lease was never observed on a non-dry-run release");
|
|
12891
|
-
debug(config, `Force-pushing version commit to ${
|
|
13053
|
+
debug(config, `Force-pushing version commit to ${branch} (lease: ${leaseTip || "(absent)"})`);
|
|
12892
13054
|
const pushResult = executor.exec("git", [
|
|
12893
13055
|
...auth.args,
|
|
12894
13056
|
"push",
|
|
12895
13057
|
auth.url,
|
|
12896
|
-
`HEAD:${
|
|
12897
|
-
`--force-with-lease=${
|
|
13058
|
+
`HEAD:${branchRef}`,
|
|
13059
|
+
`--force-with-lease=${branchRef}:${leaseTip}`
|
|
12898
13060
|
], { cwd: config.cwd });
|
|
12899
13061
|
debugExec(config, "git push", pushResult);
|
|
12900
|
-
if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${
|
|
13062
|
+
if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${branch} failed (exit code ${String(pushResult.exitCode)}). If the lease was rejected, the branch moved after this run started (expected ${leaseTip || "the branch to be absent"}) -- another run pushed a version commit, and overwriting it would discard that release. This job fails rather than clobber it.\n${pushResult.stderr || pushResult.stdout}`);
|
|
12901
13063
|
const conn = {
|
|
12902
13064
|
serverUrl: config.serverUrl,
|
|
12903
13065
|
repository: config.repository,
|
|
12904
13066
|
token: config.token
|
|
12905
13067
|
};
|
|
12906
|
-
const existingPr = await findOpenPr(executor, conn,
|
|
12907
|
-
debug(config, `Existing open PR for ${
|
|
13068
|
+
const existingPr = await findOpenPr(executor, conn, branch);
|
|
13069
|
+
debug(config, `Existing open PR for ${branch}: ${existingPr === null ? "(none)" : `#${String(existingPr)}`}`);
|
|
12908
13070
|
if (existingPr === null) try {
|
|
12909
13071
|
await createPr(executor, conn, {
|
|
12910
13072
|
title,
|
|
12911
|
-
head:
|
|
12912
|
-
base:
|
|
13073
|
+
head: branch,
|
|
13074
|
+
base: config.defaultBranch,
|
|
12913
13075
|
body
|
|
12914
13076
|
});
|
|
12915
13077
|
log$1.info("Created version PR");
|
|
@@ -12919,7 +13081,7 @@ async function runVersionMode(executor, config) {
|
|
|
12919
13081
|
};
|
|
12920
13082
|
} catch (error) {
|
|
12921
13083
|
debug(config, `createPr failed, retrying as update: ${describeError$1(error)}`);
|
|
12922
|
-
const racePr = await findOpenPr(executor, conn,
|
|
13084
|
+
const racePr = await findOpenPr(executor, conn, branch);
|
|
12923
13085
|
if (racePr === null) throw error;
|
|
12924
13086
|
await updatePr(executor, conn, racePr, {
|
|
12925
13087
|
title,
|
|
@@ -13404,7 +13566,7 @@ const releaseForgejoCommand = defineCommand({
|
|
|
13404
13566
|
const result = await runRelease(buildReleaseConfig({
|
|
13405
13567
|
dryRun,
|
|
13406
13568
|
verbose: args.verbose === true || isEnvVerbose()
|
|
13407
|
-
}), createRealExecutor$1());
|
|
13569
|
+
}), createRealExecutor$1(), processCiEventSource());
|
|
13408
13570
|
out.json(releasePayload(result, dryRun));
|
|
13409
13571
|
if (result.mode === "publish") {
|
|
13410
13572
|
const outputFile = process.env["GITHUB_OUTPUT"];
|
|
@@ -13445,6 +13607,7 @@ function buildReleaseConfig(flags) {
|
|
|
13445
13607
|
return {
|
|
13446
13608
|
...resolved.conn,
|
|
13447
13609
|
cwd: process.cwd(),
|
|
13610
|
+
defaultBranch: readConfiguredDefaultBranch(process.cwd()),
|
|
13448
13611
|
dryRun: flags.dryRun ?? false,
|
|
13449
13612
|
verbose: flags.verbose ?? false
|
|
13450
13613
|
};
|
|
@@ -13458,15 +13621,16 @@ function getCurrentBranch(executor, cwd) {
|
|
|
13458
13621
|
"HEAD"
|
|
13459
13622
|
], { cwd }).stdout.trim();
|
|
13460
13623
|
}
|
|
13461
|
-
async function runRelease(config, executor) {
|
|
13624
|
+
async function runRelease(config, executor, ciEvent) {
|
|
13462
13625
|
debug(config, `Repository: ${config.repository} on ${config.serverUrl}`);
|
|
13463
13626
|
debug(config, `Config: dryRun=${String(config.dryRun)}`);
|
|
13464
13627
|
const branch = getCurrentBranch(executor, config.cwd);
|
|
13465
13628
|
debug(config, `Current branch: ${branch}`);
|
|
13466
|
-
if (branch !==
|
|
13467
|
-
debug(config, `Skipping release
|
|
13629
|
+
if (branch !== config.defaultBranch) {
|
|
13630
|
+
debug(config, `Skipping release: ${branch} is not the configured default branch (${config.defaultBranch})`);
|
|
13468
13631
|
return { mode: "none" };
|
|
13469
13632
|
}
|
|
13633
|
+
refuseForgeDefaultMismatch(config.defaultBranch, ciEvent, "release:changesets");
|
|
13470
13634
|
const identity = await configureGitIdentity(executor, "forgejo", config.cwd, config);
|
|
13471
13635
|
log$1.info(`Git identity: ${identity.name} <${identity.email}>`);
|
|
13472
13636
|
const changesetFiles = executor.listChangesetFiles(config.cwd);
|
|
@@ -13668,7 +13832,7 @@ const releaseTriggerCommand = defineCommand({
|
|
|
13668
13832
|
args: {
|
|
13669
13833
|
ref: {
|
|
13670
13834
|
type: "string",
|
|
13671
|
-
description: "Git ref to trigger on (default: main)",
|
|
13835
|
+
description: "Git ref to trigger on (default: .tooling.json#defaultBranch, else main)",
|
|
13672
13836
|
required: false
|
|
13673
13837
|
},
|
|
13674
13838
|
bump: {
|
|
@@ -13688,7 +13852,7 @@ const releaseTriggerCommand = defineCommand({
|
|
|
13688
13852
|
},
|
|
13689
13853
|
async run({ args }) {
|
|
13690
13854
|
const out = createCliOutput(args.json === true);
|
|
13691
|
-
const ref = args.ref ??
|
|
13855
|
+
const ref = args.ref ?? readConfiguredDefaultBranch(process.cwd());
|
|
13692
13856
|
const inputs = {};
|
|
13693
13857
|
if (args.bump) inputs["bump"] = args.bump;
|
|
13694
13858
|
if (args.prerelease) inputs["prerelease"] = args.prerelease;
|
|
@@ -13705,33 +13869,131 @@ const releaseTriggerCommand = defineCommand({
|
|
|
13705
13869
|
}
|
|
13706
13870
|
});
|
|
13707
13871
|
//#endregion
|
|
13708
|
-
//#region ../forgejo/src/
|
|
13709
|
-
const
|
|
13872
|
+
//#region ../forgejo/src/label-provisioning.ts
|
|
13873
|
+
const LabelSchema$1 = z.object({
|
|
13710
13874
|
id: z.number(),
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
html_url: z.string().nullish(),
|
|
13716
|
-
assets: AttachmentListSchema.nullish()
|
|
13875
|
+
name: z.string(),
|
|
13876
|
+
color: z.string().optional(),
|
|
13877
|
+
description: z.string().optional(),
|
|
13878
|
+
exclusive: z.boolean().optional()
|
|
13717
13879
|
});
|
|
13718
|
-
const
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13880
|
+
const LabelListSchema = z.array(LabelSchema$1);
|
|
13881
|
+
const LABELS_PAGE_SIZE = 100;
|
|
13882
|
+
const LABELS_MAX_PAGES = 20;
|
|
13883
|
+
async function fetchAllForgejoLabels(executor, conn) {
|
|
13884
|
+
const labels = [];
|
|
13885
|
+
for (let page = 1; page <= LABELS_MAX_PAGES; page++) {
|
|
13886
|
+
const res = await executor.fetch(`${conn.serverUrl}/api/v1/repos/${conn.repository}/labels?limit=${String(LABELS_PAGE_SIZE)}&page=${String(page)}`, { headers: authHeaders$2(conn) });
|
|
13887
|
+
if (!res.ok) await throwForgejoFailure(res, {
|
|
13888
|
+
verb: "list labels",
|
|
13889
|
+
resource: conn.repository
|
|
13890
|
+
}, repoNotFoundMessage(conn));
|
|
13891
|
+
const parsed = LabelListSchema.safeParse(await res.json());
|
|
13892
|
+
if (!parsed.success) throw new UnexpectedError(`Unexpected label-list response: ${parsed.error.message}`);
|
|
13893
|
+
labels.push(...parsed.data);
|
|
13894
|
+
if (parsed.data.length < LABELS_PAGE_SIZE) break;
|
|
13895
|
+
}
|
|
13896
|
+
return labels;
|
|
13723
13897
|
}
|
|
13724
|
-
function
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
return
|
|
13898
|
+
async function resolveLabelIds(executor, conn, names) {
|
|
13899
|
+
if (names.length === 0) return [];
|
|
13900
|
+
const labels = await fetchAllForgejoLabels(executor, conn);
|
|
13901
|
+
return names.map((name) => {
|
|
13902
|
+
const match = labels.find((label) => label.name === name);
|
|
13903
|
+
if (match === void 0) {
|
|
13904
|
+
const known = labels.map((label) => label.name).join(", ");
|
|
13905
|
+
throw new FatalError(`No label named "${name}" found among the repository's labels in ${conn.repository} (organization-level labels are not searched). Known labels: ${known}`);
|
|
13906
|
+
}
|
|
13907
|
+
return match.id;
|
|
13908
|
+
});
|
|
13728
13909
|
}
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13910
|
+
const LabelSpecSchema = z.strictObject({
|
|
13911
|
+
name: z.string().min(1),
|
|
13912
|
+
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
13913
|
+
description: z.string(),
|
|
13914
|
+
exclusive: z.boolean().optional()
|
|
13915
|
+
});
|
|
13916
|
+
z.record(z.string(), z.array(LabelSpecSchema));
|
|
13917
|
+
//#endregion
|
|
13918
|
+
//#region ../forgejo/src/issue-labels.ts
|
|
13919
|
+
const TimelineEntrySchema = z.object({
|
|
13920
|
+
id: z.number(),
|
|
13921
|
+
type: z.string(),
|
|
13922
|
+
body: z.string().nullish(),
|
|
13923
|
+
created_at: z.string(),
|
|
13924
|
+
label: z.object({ name: z.string() }).nullish(),
|
|
13925
|
+
user: z.object({ login: z.string() }).nullish()
|
|
13926
|
+
});
|
|
13927
|
+
z.array(TimelineEntrySchema).nullable();
|
|
13928
|
+
async function resolveLabelId(executor, conn, name, labelIds) {
|
|
13929
|
+
const cached = labelIds?.get(name);
|
|
13930
|
+
if (cached !== void 0) return cached;
|
|
13931
|
+
const [id] = await resolveLabelIds(executor, conn, [name]);
|
|
13932
|
+
if (id === void 0) throw new UnexpectedError(`Label id resolution returned nothing for "${name}"`);
|
|
13933
|
+
return id;
|
|
13934
|
+
}
|
|
13935
|
+
async function addForgejoIssueLabel(executor, conn, number, name, labelIds) {
|
|
13936
|
+
const id = await resolveLabelId(executor, conn, name, labelIds);
|
|
13937
|
+
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels`, {
|
|
13938
|
+
method: "POST",
|
|
13939
|
+
headers: jsonHeaders(conn),
|
|
13940
|
+
body: JSON.stringify({ labels: [id] })
|
|
13941
|
+
}, { idempotent: true });
|
|
13942
|
+
if (!res.ok) await throwForgejoFailure(res, {
|
|
13943
|
+
verb: "add label",
|
|
13944
|
+
resource: `#${String(number)}`
|
|
13945
|
+
}, issueNotFoundMessage(conn, number));
|
|
13946
|
+
}
|
|
13947
|
+
async function removeForgejoIssueLabel(executor, conn, number, name, labelIds) {
|
|
13948
|
+
const id = await resolveLabelId(executor, conn, name, labelIds);
|
|
13949
|
+
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels/${String(id)}`, {
|
|
13950
|
+
method: "DELETE",
|
|
13951
|
+
headers: authHeaders$2(conn)
|
|
13952
|
+
}, { idempotent: true });
|
|
13953
|
+
if (!res.ok) await throwForgejoFailure(res, {
|
|
13954
|
+
verb: "remove label",
|
|
13955
|
+
resource: `#${String(number)}`
|
|
13956
|
+
}, issueNotFoundMessage(conn, number));
|
|
13957
|
+
}
|
|
13958
|
+
async function replaceForgejoIssueLabels(executor, conn, number, labelIds) {
|
|
13959
|
+
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels`, {
|
|
13960
|
+
method: "PUT",
|
|
13961
|
+
headers: jsonHeaders(conn),
|
|
13962
|
+
body: JSON.stringify({ labels: labelIds })
|
|
13963
|
+
}, { idempotent: true });
|
|
13964
|
+
if (!res.ok) await throwForgejoFailure(res, {
|
|
13965
|
+
verb: "replace labels",
|
|
13966
|
+
resource: `#${String(number)}`
|
|
13967
|
+
}, issueNotFoundMessage(conn, number));
|
|
13968
|
+
}
|
|
13969
|
+
//#endregion
|
|
13970
|
+
//#region ../forgejo/src/issue-comments.ts
|
|
13971
|
+
const CommentResponseSchema = z.object({
|
|
13972
|
+
id: z.number(),
|
|
13973
|
+
body: z.string().nullish(),
|
|
13974
|
+
user: z.object({ login: z.string() }).nullish(),
|
|
13975
|
+
created_at: z.string(),
|
|
13976
|
+
updated_at: z.string().nullish(),
|
|
13977
|
+
html_url: z.string().nullish(),
|
|
13978
|
+
assets: AttachmentListSchema.nullish()
|
|
13979
|
+
});
|
|
13980
|
+
const CommentListSchema = z.array(CommentResponseSchema);
|
|
13981
|
+
function parseCommentOrThrow(raw) {
|
|
13982
|
+
const parsed = CommentResponseSchema.safeParse(raw);
|
|
13983
|
+
if (!parsed.success) throw new UnexpectedError(`Unexpected comment response: ${parsed.error.message}`);
|
|
13984
|
+
return parsed.data;
|
|
13985
|
+
}
|
|
13986
|
+
function parseCommentListOrThrow(raw) {
|
|
13987
|
+
const parsed = CommentListSchema.safeParse(raw);
|
|
13988
|
+
if (!parsed.success) throw new UnexpectedError(`Unexpected comment-list response: ${parsed.error.message}`);
|
|
13989
|
+
return parsed.data;
|
|
13990
|
+
}
|
|
13991
|
+
function normalizeComment(raw) {
|
|
13992
|
+
return {
|
|
13993
|
+
id: raw.id,
|
|
13994
|
+
author: raw.user?.login ?? "",
|
|
13995
|
+
createdAt: raw.created_at,
|
|
13996
|
+
updatedAt: raw.updated_at ?? raw.created_at,
|
|
13735
13997
|
body: raw.body ?? "",
|
|
13736
13998
|
...raw.html_url === void 0 || raw.html_url === null ? {} : { url: raw.html_url },
|
|
13737
13999
|
attachments: (raw.assets ?? []).map(normalizeAttachment)
|
|
@@ -13780,52 +14042,6 @@ async function addForgejoComment(executor, conn, number, body) {
|
|
|
13780
14042
|
return normalizeComment(parseCommentOrThrow(await res.json()));
|
|
13781
14043
|
}
|
|
13782
14044
|
//#endregion
|
|
13783
|
-
//#region ../forgejo/src/label-provisioning.ts
|
|
13784
|
-
const LabelSchema$1 = z.object({
|
|
13785
|
-
id: z.number(),
|
|
13786
|
-
name: z.string(),
|
|
13787
|
-
color: z.string().optional(),
|
|
13788
|
-
description: z.string().optional(),
|
|
13789
|
-
exclusive: z.boolean().optional()
|
|
13790
|
-
});
|
|
13791
|
-
const LabelListSchema = z.array(LabelSchema$1);
|
|
13792
|
-
const LABELS_PAGE_SIZE = 100;
|
|
13793
|
-
const LABELS_MAX_PAGES = 20;
|
|
13794
|
-
async function fetchAllForgejoLabels(executor, conn) {
|
|
13795
|
-
const labels = [];
|
|
13796
|
-
for (let page = 1; page <= LABELS_MAX_PAGES; page++) {
|
|
13797
|
-
const res = await executor.fetch(`${conn.serverUrl}/api/v1/repos/${conn.repository}/labels?limit=${String(LABELS_PAGE_SIZE)}&page=${String(page)}`, { headers: authHeaders$2(conn) });
|
|
13798
|
-
if (!res.ok) await throwForgejoFailure(res, {
|
|
13799
|
-
verb: "list labels",
|
|
13800
|
-
resource: conn.repository
|
|
13801
|
-
}, repoNotFoundMessage(conn));
|
|
13802
|
-
const parsed = LabelListSchema.safeParse(await res.json());
|
|
13803
|
-
if (!parsed.success) throw new UnexpectedError(`Unexpected label-list response: ${parsed.error.message}`);
|
|
13804
|
-
labels.push(...parsed.data);
|
|
13805
|
-
if (parsed.data.length < LABELS_PAGE_SIZE) break;
|
|
13806
|
-
}
|
|
13807
|
-
return labels;
|
|
13808
|
-
}
|
|
13809
|
-
async function resolveLabelIds(executor, conn, names) {
|
|
13810
|
-
if (names.length === 0) return [];
|
|
13811
|
-
const labels = await fetchAllForgejoLabels(executor, conn);
|
|
13812
|
-
return names.map((name) => {
|
|
13813
|
-
const match = labels.find((label) => label.name === name);
|
|
13814
|
-
if (match === void 0) {
|
|
13815
|
-
const known = labels.map((label) => label.name).join(", ");
|
|
13816
|
-
throw new FatalError(`No label named "${name}" found among the repository's labels in ${conn.repository} (organization-level labels are not searched). Known labels: ${known}`);
|
|
13817
|
-
}
|
|
13818
|
-
return match.id;
|
|
13819
|
-
});
|
|
13820
|
-
}
|
|
13821
|
-
const LabelSpecSchema = z.strictObject({
|
|
13822
|
-
name: z.string().min(1),
|
|
13823
|
-
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
13824
|
-
description: z.string(),
|
|
13825
|
-
exclusive: z.boolean().optional()
|
|
13826
|
-
});
|
|
13827
|
-
z.record(z.string(), z.array(LabelSpecSchema));
|
|
13828
|
-
//#endregion
|
|
13829
14045
|
//#region ../forgejo/src/issues.ts
|
|
13830
14046
|
const IssueResponseSchema = z.object({
|
|
13831
14047
|
number: z.number(),
|
|
@@ -14271,6 +14487,178 @@ function resolveIssueTransport(executor, cwd, target, injectedToken, signal = nu
|
|
|
14271
14487
|
};
|
|
14272
14488
|
}
|
|
14273
14489
|
//#endregion
|
|
14490
|
+
//#region src/work-status/labels.ts
|
|
14491
|
+
const SCOPE_ORDER = [
|
|
14492
|
+
"agent",
|
|
14493
|
+
"agent-flags",
|
|
14494
|
+
"agent-note",
|
|
14495
|
+
"priority",
|
|
14496
|
+
"other"
|
|
14497
|
+
];
|
|
14498
|
+
function scopeOf(name) {
|
|
14499
|
+
const separator = name.indexOf("/");
|
|
14500
|
+
if (separator < 0) return "other";
|
|
14501
|
+
const prefix = name.slice(0, separator);
|
|
14502
|
+
if (prefix === "agent" || prefix === "agent-flags" || prefix === "agent-note" || prefix === "priority") return prefix;
|
|
14503
|
+
return "other";
|
|
14504
|
+
}
|
|
14505
|
+
function groupLabels(names, descriptions) {
|
|
14506
|
+
const groups = [];
|
|
14507
|
+
for (const scope of SCOPE_ORDER) {
|
|
14508
|
+
const inScope = names.filter((name) => scopeOf(name) === scope).toSorted((a, b) => a.localeCompare(b)).map((name) => ({
|
|
14509
|
+
name,
|
|
14510
|
+
description: descriptions.get(name) ?? null
|
|
14511
|
+
}));
|
|
14512
|
+
if (inScope.length === 0) continue;
|
|
14513
|
+
groups.push({
|
|
14514
|
+
scope,
|
|
14515
|
+
labels: inScope
|
|
14516
|
+
});
|
|
14517
|
+
}
|
|
14518
|
+
return groups;
|
|
14519
|
+
}
|
|
14520
|
+
const RUNNING_LABEL = /^agent\/[a-z-]+-running$/;
|
|
14521
|
+
const HAND_AUTHORED_LABEL = "agent-flags/hand-authored";
|
|
14522
|
+
function liftedFacts(names) {
|
|
14523
|
+
return {
|
|
14524
|
+
runLive: names.filter((name) => RUNNING_LABEL.test(name)).toSorted(),
|
|
14525
|
+
heldByHand: names.includes(HAND_AUTHORED_LABEL)
|
|
14526
|
+
};
|
|
14527
|
+
}
|
|
14528
|
+
function labelNamesOf(section) {
|
|
14529
|
+
const names = [];
|
|
14530
|
+
for (const group of [...section.pr, ...section.issue]) for (const label of group.labels) names.push(label.name);
|
|
14531
|
+
return names;
|
|
14532
|
+
}
|
|
14533
|
+
function ownerOf(names) {
|
|
14534
|
+
const agent = names.filter((name) => name.startsWith("agent/")).toSorted();
|
|
14535
|
+
const running = agent.find((name) => RUNNING_LABEL.test(name));
|
|
14536
|
+
if (running !== void 0) return {
|
|
14537
|
+
owner: "agent",
|
|
14538
|
+
label: running,
|
|
14539
|
+
running: true
|
|
14540
|
+
};
|
|
14541
|
+
if (names.includes("agent-flags/hand-authored")) return { owner: "person" };
|
|
14542
|
+
const held = agent[0];
|
|
14543
|
+
if (held !== void 0) return {
|
|
14544
|
+
owner: "agent",
|
|
14545
|
+
label: held,
|
|
14546
|
+
running: false
|
|
14547
|
+
};
|
|
14548
|
+
return { owner: "unclaimed" };
|
|
14549
|
+
}
|
|
14550
|
+
//#endregion
|
|
14551
|
+
//#region src/work-status/claim.ts
|
|
14552
|
+
const HANDOVER_ROUTES = "Claim it from the control panel's \"Claim for hand work\", or remove the agent label by hand and run bst work:start again.";
|
|
14553
|
+
function decideClaim(report, enabled) {
|
|
14554
|
+
if (!enabled) return {
|
|
14555
|
+
kind: "answer",
|
|
14556
|
+
exitCode: 0,
|
|
14557
|
+
result: {
|
|
14558
|
+
outcome: "not-agent-repo",
|
|
14559
|
+
reason: "Nothing was claimed: this repository's default branch does not set agent.enabled, so the agent does not work it and there is nothing to claim against."
|
|
14560
|
+
}
|
|
14561
|
+
};
|
|
14562
|
+
if (report.owner === null) return {
|
|
14563
|
+
kind: "answer",
|
|
14564
|
+
exitCode: 1,
|
|
14565
|
+
result: {
|
|
14566
|
+
outcome: "failed",
|
|
14567
|
+
reason: "A label read failed, so a run may be live on this object unseen. Nothing was claimed; retry once the forge read works."
|
|
14568
|
+
}
|
|
14569
|
+
};
|
|
14570
|
+
const held = ownershipOf(report);
|
|
14571
|
+
if (held.kind === "running") return {
|
|
14572
|
+
kind: "answer",
|
|
14573
|
+
exitCode: 1,
|
|
14574
|
+
result: {
|
|
14575
|
+
outcome: "refused",
|
|
14576
|
+
label: held.label,
|
|
14577
|
+
reason: `A run is live on this object (${held.label}); a claim does not stop it.`
|
|
14578
|
+
}
|
|
14579
|
+
};
|
|
14580
|
+
if (held.kind === "person") return {
|
|
14581
|
+
kind: "answer",
|
|
14582
|
+
exitCode: 0,
|
|
14583
|
+
result: { outcome: "already-claimed" }
|
|
14584
|
+
};
|
|
14585
|
+
if (held.kind === "agent") return {
|
|
14586
|
+
kind: "answer",
|
|
14587
|
+
exitCode: 1,
|
|
14588
|
+
result: {
|
|
14589
|
+
outcome: "refused",
|
|
14590
|
+
label: held.label,
|
|
14591
|
+
reason: `The agent holds this object (${held.label}). ${HANDOVER_ROUTES}`
|
|
14592
|
+
}
|
|
14593
|
+
};
|
|
14594
|
+
return { kind: "write" };
|
|
14595
|
+
}
|
|
14596
|
+
function ownershipOf(report) {
|
|
14597
|
+
if (report.labels.state === "ok") {
|
|
14598
|
+
const ownership = ownerOf(labelNamesOf(report.labels));
|
|
14599
|
+
if (ownership.owner === "agent") return ownership.running ? {
|
|
14600
|
+
kind: "running",
|
|
14601
|
+
label: ownership.label
|
|
14602
|
+
} : {
|
|
14603
|
+
kind: "agent",
|
|
14604
|
+
label: ownership.label
|
|
14605
|
+
};
|
|
14606
|
+
if (ownership.owner === "person") return { kind: "person" };
|
|
14607
|
+
return { kind: "unclaimed" };
|
|
14608
|
+
}
|
|
14609
|
+
if (report.owner === "agent") {
|
|
14610
|
+
const live = report.runLive?.[0];
|
|
14611
|
+
return live === void 0 ? {
|
|
14612
|
+
kind: "agent",
|
|
14613
|
+
label: "an agent label"
|
|
14614
|
+
} : {
|
|
14615
|
+
kind: "running",
|
|
14616
|
+
label: live
|
|
14617
|
+
};
|
|
14618
|
+
}
|
|
14619
|
+
return report.owner === "person" ? { kind: "person" } : { kind: "unclaimed" };
|
|
14620
|
+
}
|
|
14621
|
+
async function writeClaim(executor, cwd, target, number) {
|
|
14622
|
+
try {
|
|
14623
|
+
const { conn } = requireForgejoTransport(executor, cwd, target, "bst work:start claims work on Forgejo, the forge the agent drives; a GitHub checkout has no agent to hand work to.");
|
|
14624
|
+
const flag = (await fetchAllForgejoLabels(executor, conn)).find((label) => label.name === HAND_AUTHORED_LABEL);
|
|
14625
|
+
if (flag === void 0) return {
|
|
14626
|
+
outcome: "failed",
|
|
14627
|
+
reason: `The forge has no label named ${HAND_AUTHORED_LABEL}. Provision the agent's labels first with bst-agent agent:labels --repo ${conn.repository}, or the control panel's Provision labels.`
|
|
14628
|
+
};
|
|
14629
|
+
await addForgejoIssueLabel(executor, conn, number, HAND_AUTHORED_LABEL, /* @__PURE__ */ new Map([[HAND_AUTHORED_LABEL, flag.id]]));
|
|
14630
|
+
return {
|
|
14631
|
+
outcome: "claimed",
|
|
14632
|
+
label: HAND_AUTHORED_LABEL
|
|
14633
|
+
};
|
|
14634
|
+
} catch (error) {
|
|
14635
|
+
return {
|
|
14636
|
+
outcome: "failed",
|
|
14637
|
+
reason: describeError$1(error)
|
|
14638
|
+
};
|
|
14639
|
+
}
|
|
14640
|
+
}
|
|
14641
|
+
//#endregion
|
|
14642
|
+
//#region src/work-status/consent.ts
|
|
14643
|
+
async function readAgentConsent(git, defaultBranch) {
|
|
14644
|
+
const onBranch = await git.show(`origin/${defaultBranch}`, ".tooling.json");
|
|
14645
|
+
if (onBranch !== null) return {
|
|
14646
|
+
enabled: agentEnabledIn(onBranch),
|
|
14647
|
+
source: "default-branch"
|
|
14648
|
+
};
|
|
14649
|
+
return {
|
|
14650
|
+
enabled: agentEnabledIn(readWorkingTree(git.topLevel)),
|
|
14651
|
+
source: "working-tree"
|
|
14652
|
+
};
|
|
14653
|
+
}
|
|
14654
|
+
function readWorkingTree(topLevel) {
|
|
14655
|
+
try {
|
|
14656
|
+
return readFileSync(path.join(topLevel, ".tooling.json"), "utf8");
|
|
14657
|
+
} catch {
|
|
14658
|
+
return;
|
|
14659
|
+
}
|
|
14660
|
+
}
|
|
14661
|
+
//#endregion
|
|
14274
14662
|
//#region ../forgejo/src/issue-run.ts
|
|
14275
14663
|
var NothingToUpdateError = class extends FatalError {
|
|
14276
14664
|
constructor() {
|
|
@@ -15162,6 +15550,42 @@ async function createWorkGit(executor, cwd) {
|
|
|
15162
15550
|
};
|
|
15163
15551
|
}
|
|
15164
15552
|
//#endregion
|
|
15553
|
+
//#region src/work-status/prepare.ts
|
|
15554
|
+
function tryResolveWorkForge(executor, topLevel, target) {
|
|
15555
|
+
try {
|
|
15556
|
+
return resolveWorkForge(executor, topLevel, target);
|
|
15557
|
+
} catch (error) {
|
|
15558
|
+
return error instanceof Error ? error : new UnexpectedError(String(error));
|
|
15559
|
+
}
|
|
15560
|
+
}
|
|
15561
|
+
function sameRepository(one, other) {
|
|
15562
|
+
return one.toLowerCase() === other.toLowerCase();
|
|
15563
|
+
}
|
|
15564
|
+
function refNamesAnotherRepository(checkoutRepository, refRepository) {
|
|
15565
|
+
return new FatalError(`Usage: this command reads this checkout's repository (${checkoutRepository}); the ref names ${refRepository}`);
|
|
15566
|
+
}
|
|
15567
|
+
async function prepareWorkStatus(refText, cwd) {
|
|
15568
|
+
const ref = refText === "" ? null : parseIssueRef(refText);
|
|
15569
|
+
const executor = createRealExecutor$1();
|
|
15570
|
+
const git = await createWorkGit(executor, cwd);
|
|
15571
|
+
const target = ref?.target;
|
|
15572
|
+
if (target !== void 0) {
|
|
15573
|
+
const checkout = await git.originRepository();
|
|
15574
|
+
if (checkout !== null && !sameRepository(target.repository, checkout)) throw refNamesAnotherRepository(checkout, target.repository);
|
|
15575
|
+
}
|
|
15576
|
+
let forge = tryResolveWorkForge(executor, git.topLevel);
|
|
15577
|
+
if (target !== void 0 && !(forge instanceof Error)) {
|
|
15578
|
+
if (!sameRepository(target.repository, forge.repository)) throw refNamesAnotherRepository(forge.repository, target.repository);
|
|
15579
|
+
forge = tryResolveWorkForge(executor, git.topLevel, target);
|
|
15580
|
+
}
|
|
15581
|
+
return {
|
|
15582
|
+
ref,
|
|
15583
|
+
executor,
|
|
15584
|
+
git,
|
|
15585
|
+
forge
|
|
15586
|
+
};
|
|
15587
|
+
}
|
|
15588
|
+
//#endregion
|
|
15165
15589
|
//#region src/docs-check/discover.ts
|
|
15166
15590
|
const DOCS_ROOT = "docs";
|
|
15167
15591
|
const RETIRED_DIR = "retired";
|
|
@@ -15251,7 +15675,7 @@ function retireDestination(src, verb) {
|
|
|
15251
15675
|
if (dir === DOCS_ROOT && LEGACY_ACTIVE_PATTERN.test(base)) return `${DOCS_ROOT}/${RETIRED_DIR}/${verb}-${base.slice(5)}`;
|
|
15252
15676
|
return `${dir}/${RETIRED_DIR}/${base}`;
|
|
15253
15677
|
}
|
|
15254
|
-
function
|
|
15678
|
+
function candidatePairs(exec, config) {
|
|
15255
15679
|
const candidates = [];
|
|
15256
15680
|
for (const active of [...conventionalActiveDirs(exec), ...config.extraDirs]) candidates.push({
|
|
15257
15681
|
active,
|
|
@@ -15266,8 +15690,16 @@ function discoverPairs(exec, config) {
|
|
|
15266
15690
|
activePattern: LEGACY_ACTIVE_PATTERN,
|
|
15267
15691
|
enforced: isEnforced(DOCS_ROOT, config.enforced)
|
|
15268
15692
|
});
|
|
15693
|
+
return candidates;
|
|
15694
|
+
}
|
|
15695
|
+
function matchableActiveDirs(exec, config) {
|
|
15696
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15697
|
+
for (const pair of candidatePairs(exec, config)) if (exec.readDir(pair.active) !== void 0) seen.add(pair.active);
|
|
15698
|
+
return [...seen].toSorted();
|
|
15699
|
+
}
|
|
15700
|
+
function discoverPairs(exec, config) {
|
|
15269
15701
|
const seen = /* @__PURE__ */ new Set();
|
|
15270
|
-
return
|
|
15702
|
+
return candidatePairs(exec, config).filter((pair) => {
|
|
15271
15703
|
if (seen.has(pair.active)) return false;
|
|
15272
15704
|
if (!(listDocs(exec, pair.active, pair.activePattern).length > 0 || pair.retiredDirs.some((dir) => listDocs(exec, dir).length > 0))) return false;
|
|
15273
15705
|
seen.add(pair.active);
|
|
@@ -17416,45 +17848,6 @@ function feedbackSince(comments, headCommitDate, cap, agentLogin) {
|
|
|
17416
17848
|
};
|
|
17417
17849
|
}
|
|
17418
17850
|
//#endregion
|
|
17419
|
-
//#region src/work-status/labels.ts
|
|
17420
|
-
const SCOPE_ORDER = [
|
|
17421
|
-
"agent",
|
|
17422
|
-
"agent-flags",
|
|
17423
|
-
"agent-note",
|
|
17424
|
-
"priority",
|
|
17425
|
-
"other"
|
|
17426
|
-
];
|
|
17427
|
-
function scopeOf(name) {
|
|
17428
|
-
const separator = name.indexOf("/");
|
|
17429
|
-
if (separator < 0) return "other";
|
|
17430
|
-
const prefix = name.slice(0, separator);
|
|
17431
|
-
if (prefix === "agent" || prefix === "agent-flags" || prefix === "agent-note" || prefix === "priority") return prefix;
|
|
17432
|
-
return "other";
|
|
17433
|
-
}
|
|
17434
|
-
function groupLabels(names, descriptions) {
|
|
17435
|
-
const groups = [];
|
|
17436
|
-
for (const scope of SCOPE_ORDER) {
|
|
17437
|
-
const inScope = names.filter((name) => scopeOf(name) === scope).toSorted((a, b) => a.localeCompare(b)).map((name) => ({
|
|
17438
|
-
name,
|
|
17439
|
-
description: descriptions.get(name) ?? null
|
|
17440
|
-
}));
|
|
17441
|
-
if (inScope.length === 0) continue;
|
|
17442
|
-
groups.push({
|
|
17443
|
-
scope,
|
|
17444
|
-
labels: inScope
|
|
17445
|
-
});
|
|
17446
|
-
}
|
|
17447
|
-
return groups;
|
|
17448
|
-
}
|
|
17449
|
-
const RUNNING_LABEL = /^agent\/[a-z-]+-running$/;
|
|
17450
|
-
const HAND_AUTHORED_LABEL = "agent-flags/hand-authored";
|
|
17451
|
-
function liftedFacts(names) {
|
|
17452
|
-
return {
|
|
17453
|
-
runLive: names.filter((name) => RUNNING_LABEL.test(name)).toSorted(),
|
|
17454
|
-
heldByHand: names.includes(HAND_AUTHORED_LABEL)
|
|
17455
|
-
};
|
|
17456
|
-
}
|
|
17457
|
-
//#endregion
|
|
17458
17851
|
//#region src/work-status/report.ts
|
|
17459
17852
|
const COMMIT_CAP = 20;
|
|
17460
17853
|
const FEEDBACK_CAP = 20;
|
|
@@ -17690,11 +18083,14 @@ function objectSection({ resolved, forgeDown }) {
|
|
|
17690
18083
|
return ok(facts);
|
|
17691
18084
|
}
|
|
17692
18085
|
function liftedFactsOf(resolved, forgeDown) {
|
|
17693
|
-
const
|
|
18086
|
+
const names = labelNames([resolved.pr, resolved.issue]);
|
|
18087
|
+
const read = liftedFacts(names);
|
|
18088
|
+
const ownership = ownerOf(names);
|
|
17694
18089
|
const complete = forgeDown === null && resolved.prFailed === null && resolved.issueFailed === null;
|
|
17695
18090
|
return {
|
|
17696
18091
|
runLive: complete || read.runLive.length > 0 ? read.runLive : null,
|
|
17697
|
-
heldByHand: complete || read.heldByHand ? read.heldByHand : null
|
|
18092
|
+
heldByHand: complete || read.heldByHand ? read.heldByHand : null,
|
|
18093
|
+
owner: complete || ownership.owner !== "unclaimed" ? ownership.owner : null
|
|
17698
18094
|
};
|
|
17699
18095
|
}
|
|
17700
18096
|
async function docsSection({ git, docsExtraDirs, resolved }) {
|
|
@@ -17810,6 +18206,7 @@ async function buildWorkStatus(deps, ref) {
|
|
|
17810
18206
|
},
|
|
17811
18207
|
runLive: lifted.runLive,
|
|
17812
18208
|
heldByHand: lifted.heldByHand,
|
|
18209
|
+
owner: lifted.owner,
|
|
17813
18210
|
object: await section(() => objectSection(inputs)),
|
|
17814
18211
|
docs: await section(() => docsSection(inputs)),
|
|
17815
18212
|
labels: await section(() => labelsSection(inputs)),
|
|
@@ -17851,13 +18248,22 @@ function heading({ kind, branch, head }) {
|
|
|
17851
18248
|
if (head !== null) named.push(`head ${short(head)}`);
|
|
17852
18249
|
return `work:status ${named.join(", ")}`;
|
|
17853
18250
|
}
|
|
17854
|
-
function
|
|
18251
|
+
function claimableNumber(report) {
|
|
18252
|
+
if (report.object.state !== "ok") return null;
|
|
18253
|
+
if (report.target.kind === "issue") return report.object.issue?.number ?? null;
|
|
18254
|
+
return report.object.pr?.number ?? report.object.issue?.number ?? null;
|
|
18255
|
+
}
|
|
18256
|
+
function renderWorkStatus(report, options) {
|
|
17855
18257
|
const quiet = nothingInFlight(report);
|
|
17856
18258
|
if (quiet !== null) return [heading(report.target), quiet].join("\n");
|
|
17857
18259
|
const lines = [heading(report.target)];
|
|
17858
18260
|
for (const label of report.runLive ?? []) lines.push(`RUN LIVE: ${label} -- a run is working this object; do not start another.`);
|
|
17859
18261
|
if (report.runLive === null || report.heldByHand === null) lines.push("UNKNOWN: whether a run is live or a human holds this -- not every label could be read.");
|
|
17860
18262
|
if (report.heldByHand === true) lines.push("HELD BY HAND: agent-flags/hand-authored -- a human claimed this one; do not drive it.");
|
|
18263
|
+
if (options?.agentEnabled === true && report.owner === "unclaimed") {
|
|
18264
|
+
const number = claimableNumber(report);
|
|
18265
|
+
if (number !== null) lines.push(`UNCLAIMED: no agent label and no hand claim -- run \`bst work:start ${String(number)}\` before you work this.`);
|
|
18266
|
+
}
|
|
17861
18267
|
lines.push(...block("object", report.object, objectRows));
|
|
17862
18268
|
lines.push(...block("docs", report.docs, (value) => {
|
|
17863
18269
|
const rows = [` base ${value.base}`];
|
|
@@ -17906,20 +18312,77 @@ function renderWorkStatus(report) {
|
|
|
17906
18312
|
return lines.join("\n");
|
|
17907
18313
|
}
|
|
17908
18314
|
//#endregion
|
|
17909
|
-
//#region src/commands/work-
|
|
17910
|
-
function
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
}
|
|
17916
|
-
}
|
|
17917
|
-
function sameRepository(one, other) {
|
|
17918
|
-
return one.toLowerCase() === other.toLowerCase();
|
|
18315
|
+
//#region src/commands/work-start.ts
|
|
18316
|
+
function claimLine(result) {
|
|
18317
|
+
if (result.outcome === "claimed") return `claim: claimed -- ${result.label ?? "agent-flags/hand-authored"} is on this object.`;
|
|
18318
|
+
if (result.outcome === "already-claimed") return "claim: already-claimed -- the flag is already on it; nothing was written.";
|
|
18319
|
+
if (result.outcome === "not-agent-repo") return `claim: not-agent-repo -- ${result.reason ?? "nothing was claimed."}`;
|
|
18320
|
+
return `claim: ${result.outcome} -- ${result.reason ?? "nothing was claimed."}`;
|
|
17919
18321
|
}
|
|
17920
|
-
function
|
|
17921
|
-
return
|
|
18322
|
+
function claimFailed(result) {
|
|
18323
|
+
return result.outcome === "refused" || result.outcome === "failed";
|
|
17922
18324
|
}
|
|
18325
|
+
const workStartCommand = defineCommand({
|
|
18326
|
+
meta: {
|
|
18327
|
+
name: "work:start",
|
|
18328
|
+
description: "Claim an issue or pull request for hand work: put agent-flags/hand-authored on it so the agent leaves it alone"
|
|
18329
|
+
},
|
|
18330
|
+
args: {
|
|
18331
|
+
ref: {
|
|
18332
|
+
type: "positional",
|
|
18333
|
+
required: true,
|
|
18334
|
+
description: "Issue or pull request number or URL to claim"
|
|
18335
|
+
},
|
|
18336
|
+
json: {
|
|
18337
|
+
type: "boolean",
|
|
18338
|
+
description: "Emit one JSON document: the whole work:status report plus a claim member {outcome, label?, reason?}. outcome is claimed (the flag was written), already-claimed (it was already on the object, nothing written), not-agent-repo (the default branch does not set agent.enabled, nothing written), refused (a live run or an agent label holds it; exit 1), or failed (a read or write could not be done; exit 1). refused and failed carry ok: false and an error member of kind runtime, and the report survives beside them."
|
|
18339
|
+
},
|
|
18340
|
+
verbose: {
|
|
18341
|
+
type: "boolean",
|
|
18342
|
+
description: "Emit detailed debug logging"
|
|
18343
|
+
}
|
|
18344
|
+
},
|
|
18345
|
+
async run({ args }) {
|
|
18346
|
+
const { ref, executor, git, forge } = await prepareWorkStatus(typeof args.ref === "string" ? args.ref.trim() : "", process.cwd());
|
|
18347
|
+
const number = ref?.number;
|
|
18348
|
+
if (number === void 0) throw new FatalError("Usage: bst work:start needs an issue or pull request number or URL");
|
|
18349
|
+
const docs = resolveDocsConfig(git.topLevel);
|
|
18350
|
+
const defaultBranch = resolveDefaultBranch({ cwd: git.topLevel });
|
|
18351
|
+
const report = await buildWorkStatus({
|
|
18352
|
+
git,
|
|
18353
|
+
forge,
|
|
18354
|
+
docsExtraDirs: docs.extraDirs,
|
|
18355
|
+
defaultBranch
|
|
18356
|
+
}, ref);
|
|
18357
|
+
const consent = await readAgentConsent(git, defaultBranch);
|
|
18358
|
+
const decision = decideClaim(report, consent.enabled);
|
|
18359
|
+
let result;
|
|
18360
|
+
let exitCode;
|
|
18361
|
+
if (decision.kind === "write") {
|
|
18362
|
+
result = await writeClaim(executor, git.topLevel, ref?.target, number);
|
|
18363
|
+
exitCode = claimFailed(result) ? 1 : 0;
|
|
18364
|
+
} else {
|
|
18365
|
+
result = decision.result;
|
|
18366
|
+
exitCode = decision.exitCode;
|
|
18367
|
+
}
|
|
18368
|
+
const out = createCliOutput(args.json === true);
|
|
18369
|
+
const failed = claimFailed(result);
|
|
18370
|
+
out.json({
|
|
18371
|
+
ok: !failed,
|
|
18372
|
+
...report,
|
|
18373
|
+
claim: result,
|
|
18374
|
+
...failed ? { error: {
|
|
18375
|
+
kind: "runtime",
|
|
18376
|
+
message: result.reason ?? result.outcome
|
|
18377
|
+
} } : {}
|
|
18378
|
+
});
|
|
18379
|
+
out.text(renderWorkStatus(report, { agentEnabled: consent.enabled }));
|
|
18380
|
+
out.text(claimLine(result));
|
|
18381
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
18382
|
+
}
|
|
18383
|
+
});
|
|
18384
|
+
//#endregion
|
|
18385
|
+
//#region src/commands/work-status.ts
|
|
17923
18386
|
const workStatusCommand = defineCommand({
|
|
17924
18387
|
meta: {
|
|
17925
18388
|
name: "work:status",
|
|
@@ -17933,7 +18396,7 @@ const workStatusCommand = defineCommand({
|
|
|
17933
18396
|
},
|
|
17934
18397
|
json: {
|
|
17935
18398
|
type: "boolean",
|
|
17936
|
-
description: "Emit one JSON document: target, runLive, heldByHand (each null when not every label could be read), and six sections (object, docs, labels, feedback, findings, commits), each {state: ok} with its fields, {state: absent, reason} or {state: unavailable, reason}. The output is advice, not a gate: the feedback cutoff is the newest agent run or review comment on the pull request and is approximate, and review comments on diff lines are not in the thread this reads."
|
|
18399
|
+
description: "Emit one JSON document: target, runLive, heldByHand, owner (agent, person or unclaimed; each null when not every label could be read), and six sections (object, docs, labels, feedback, findings, commits), each {state: ok} with its fields, {state: absent, reason} or {state: unavailable, reason}. The output is advice, not a gate: the feedback cutoff is the newest agent run or review comment on the pull request and is approximate, and review comments on diff lines are not in the thread this reads."
|
|
17937
18400
|
},
|
|
17938
18401
|
verbose: {
|
|
17939
18402
|
type: "boolean",
|
|
@@ -17941,20 +18404,7 @@ const workStatusCommand = defineCommand({
|
|
|
17941
18404
|
}
|
|
17942
18405
|
},
|
|
17943
18406
|
async run({ args }) {
|
|
17944
|
-
const
|
|
17945
|
-
const ref = refText === "" ? null : parseIssueRef(refText);
|
|
17946
|
-
const executor = createRealExecutor$1();
|
|
17947
|
-
const git = await createWorkGit(executor, process.cwd());
|
|
17948
|
-
const target = ref?.target;
|
|
17949
|
-
if (target !== void 0) {
|
|
17950
|
-
const checkout = await git.originRepository();
|
|
17951
|
-
if (checkout !== null && !sameRepository(target.repository, checkout)) throw refNamesAnotherRepository(checkout, target.repository);
|
|
17952
|
-
}
|
|
17953
|
-
let forge = tryResolveWorkForge(executor, git.topLevel);
|
|
17954
|
-
if (target !== void 0 && !(forge instanceof Error)) {
|
|
17955
|
-
if (!sameRepository(target.repository, forge.repository)) throw refNamesAnotherRepository(forge.repository, target.repository);
|
|
17956
|
-
forge = tryResolveWorkForge(executor, git.topLevel, target);
|
|
17957
|
-
}
|
|
18407
|
+
const { ref, git, forge } = await prepareWorkStatus(typeof args.ref === "string" ? args.ref.trim() : "", process.cwd());
|
|
17958
18408
|
const docs = resolveDocsConfig(git.topLevel);
|
|
17959
18409
|
const defaultBranch = resolveDefaultBranch({ cwd: git.topLevel });
|
|
17960
18410
|
const report = await buildWorkStatus({
|
|
@@ -17963,12 +18413,13 @@ const workStatusCommand = defineCommand({
|
|
|
17963
18413
|
docsExtraDirs: docs.extraDirs,
|
|
17964
18414
|
defaultBranch
|
|
17965
18415
|
}, ref);
|
|
18416
|
+
const consent = await readAgentConsent(git, defaultBranch);
|
|
17966
18417
|
const out = createCliOutput(args.json === true);
|
|
17967
18418
|
out.json({
|
|
17968
18419
|
ok: true,
|
|
17969
18420
|
...report
|
|
17970
18421
|
});
|
|
17971
|
-
out.text(renderWorkStatus(report));
|
|
18422
|
+
out.text(renderWorkStatus(report, { agentEnabled: consent.enabled }));
|
|
17972
18423
|
}
|
|
17973
18424
|
});
|
|
17974
18425
|
//#endregion
|
|
@@ -18224,8 +18675,9 @@ const PASSED_VERDICT = {
|
|
|
18224
18675
|
reason: null
|
|
18225
18676
|
};
|
|
18226
18677
|
async function mergeVersionPrForgejo(executor, conn, options) {
|
|
18227
|
-
const
|
|
18228
|
-
|
|
18678
|
+
const branch = versionPrBranch(options.defaultBranch);
|
|
18679
|
+
const prNumber = await findOpenPr(executor, conn, branch);
|
|
18680
|
+
if (prNumber === null) throw new FatalError(`No open PR found for branch ${branch}`);
|
|
18229
18681
|
const sleep$1 = options.sleep ?? sleep;
|
|
18230
18682
|
const recheckDelayMs = envMsOverride(process.env["BST_TEST_VERSION_MERGE_RECHECK_MS"], RECHECK_DELAY_MS);
|
|
18231
18683
|
const confirmation = await confirmMergeable(executor, conn, prNumber, {
|
|
@@ -18246,12 +18698,12 @@ async function mergeVersionPrForgejo(executor, conn, options) {
|
|
|
18246
18698
|
headSha,
|
|
18247
18699
|
mergeBase: decidedFrom.mergeBase,
|
|
18248
18700
|
changedFiles: decidedFrom.changedFiles,
|
|
18249
|
-
branch
|
|
18701
|
+
branch,
|
|
18250
18702
|
rechecked,
|
|
18251
18703
|
verdict: { ...PASSED_VERDICT }
|
|
18252
18704
|
};
|
|
18253
18705
|
if (options.dryRun) {
|
|
18254
|
-
log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${
|
|
18706
|
+
log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${branch}`);
|
|
18255
18707
|
return {
|
|
18256
18708
|
...outcome,
|
|
18257
18709
|
status: "would-merge"
|
|
@@ -18262,7 +18714,7 @@ async function mergeVersionPrForgejo(executor, conn, options) {
|
|
|
18262
18714
|
deleteBranch: true,
|
|
18263
18715
|
expectedHeadSha: headSha
|
|
18264
18716
|
});
|
|
18265
|
-
log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${
|
|
18717
|
+
log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${branch}`);
|
|
18266
18718
|
return {
|
|
18267
18719
|
...outcome,
|
|
18268
18720
|
status: "merged"
|
|
@@ -18277,16 +18729,17 @@ const GitHubVersionPrSchema = z.object({
|
|
|
18277
18729
|
changedFiles: z.number()
|
|
18278
18730
|
});
|
|
18279
18731
|
function mergeVersionPrGitHub(executor, options) {
|
|
18732
|
+
const branch = versionPrBranch(options.defaultBranch);
|
|
18280
18733
|
const view = executor.exec("gh", [
|
|
18281
18734
|
"pr",
|
|
18282
18735
|
"view",
|
|
18283
|
-
|
|
18736
|
+
branch,
|
|
18284
18737
|
"--json",
|
|
18285
18738
|
"number,state,isDraft,mergeable,headRefOid,changedFiles"
|
|
18286
18739
|
], { cwd: process.cwd() });
|
|
18287
|
-
if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${
|
|
18740
|
+
if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${branch}: ${view.stderr || view.stdout || "unknown error"}`);
|
|
18288
18741
|
const parsed = GitHubVersionPrSchema.safeParse(JSON.parse(view.stdout));
|
|
18289
|
-
if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${
|
|
18742
|
+
if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${branch}: ${parsed.error.message}`);
|
|
18290
18743
|
const data = parsed.data;
|
|
18291
18744
|
const emptiness = data.changedFiles === 0 ? "empty" : "non-empty";
|
|
18292
18745
|
const mergeable = data.mergeable === "CONFLICTING" ? "conflicted" : "mergeable";
|
|
@@ -18305,12 +18758,12 @@ function mergeVersionPrGitHub(executor, options) {
|
|
|
18305
18758
|
headSha,
|
|
18306
18759
|
mergeBase: null,
|
|
18307
18760
|
changedFiles: data.changedFiles,
|
|
18308
|
-
branch
|
|
18761
|
+
branch,
|
|
18309
18762
|
rechecked: false,
|
|
18310
18763
|
verdict: { ...PASSED_VERDICT }
|
|
18311
18764
|
};
|
|
18312
18765
|
if (options.dryRun) {
|
|
18313
|
-
log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${
|
|
18766
|
+
log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${branch}`);
|
|
18314
18767
|
return {
|
|
18315
18768
|
...outcome,
|
|
18316
18769
|
status: "would-merge"
|
|
@@ -18319,14 +18772,14 @@ function mergeVersionPrGitHub(executor, options) {
|
|
|
18319
18772
|
const result = executor.exec("gh", [
|
|
18320
18773
|
"pr",
|
|
18321
18774
|
"merge",
|
|
18322
|
-
|
|
18775
|
+
branch,
|
|
18323
18776
|
"--merge",
|
|
18324
18777
|
"--delete-branch",
|
|
18325
18778
|
"--match-head-commit",
|
|
18326
18779
|
headSha
|
|
18327
18780
|
], { cwd: process.cwd() });
|
|
18328
18781
|
if (result.exitCode !== 0) throw new FatalError(`Failed to merge PR: ${result.stderr || result.stdout || "unknown error"}`);
|
|
18329
|
-
log$1.info(`Merged changesets PR and deleted branch ${
|
|
18782
|
+
log$1.info(`Merged changesets PR and deleted branch ${branch}`);
|
|
18330
18783
|
return {
|
|
18331
18784
|
...outcome,
|
|
18332
18785
|
status: "merged"
|
|
@@ -18354,7 +18807,14 @@ const releaseMergeCommand = defineCommand({
|
|
|
18354
18807
|
const dryRun = args["dry-run"] === true;
|
|
18355
18808
|
const resolved = resolveConnection(process.cwd());
|
|
18356
18809
|
const executor = createRealExecutor$1();
|
|
18357
|
-
const
|
|
18810
|
+
const defaultBranch = readConfiguredDefaultBranch(process.cwd());
|
|
18811
|
+
const result = resolved.type === "forgejo" ? await mergeVersionPrForgejo(executor, resolved.conn, {
|
|
18812
|
+
dryRun,
|
|
18813
|
+
defaultBranch
|
|
18814
|
+
}) : mergeVersionPrGitHub(executor, {
|
|
18815
|
+
dryRun,
|
|
18816
|
+
defaultBranch
|
|
18817
|
+
});
|
|
18358
18818
|
out.json(result);
|
|
18359
18819
|
}
|
|
18360
18820
|
});
|
|
@@ -20115,7 +20575,7 @@ function composePs(executor, config) {
|
|
|
20115
20575
|
//#region src/docker-check/health.ts
|
|
20116
20576
|
const DOCKER_RUN_START_FAILURE_EXIT_CODE = 125;
|
|
20117
20577
|
const PROBE_TIMEOUT_SECONDS = "5";
|
|
20118
|
-
const PROBE_IMAGE = "curlimages/curl:8.
|
|
20578
|
+
const PROBE_IMAGE = "curlimages/curl:8.22.0@sha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777";
|
|
20119
20579
|
function getContainerHealth(containers, serviceName) {
|
|
20120
20580
|
return containers.find((c) => c.Service === serviceName)?.Health ?? "unknown";
|
|
20121
20581
|
}
|
|
@@ -20814,6 +21274,102 @@ async function runHooksCheck(config) {
|
|
|
20814
21274
|
};
|
|
20815
21275
|
}
|
|
20816
21276
|
//#endregion
|
|
21277
|
+
//#region src/commands/setup-agent.ts
|
|
21278
|
+
const RawToolingConfigSchema = z.record(z.string(), z.unknown());
|
|
21279
|
+
function previousState(saved) {
|
|
21280
|
+
const enabled = saved.agent?.enabled;
|
|
21281
|
+
if (enabled === void 0) return "absent";
|
|
21282
|
+
if (enabled === true) return "true";
|
|
21283
|
+
return "false";
|
|
21284
|
+
}
|
|
21285
|
+
function withAgentEnabled(raw) {
|
|
21286
|
+
const agent = RawToolingConfigSchema.safeParse(raw["agent"]);
|
|
21287
|
+
const block = agent.success ? { ...agent.data } : {};
|
|
21288
|
+
block["enabled"] = true;
|
|
21289
|
+
return {
|
|
21290
|
+
...raw,
|
|
21291
|
+
agent: block
|
|
21292
|
+
};
|
|
21293
|
+
}
|
|
21294
|
+
const CHANGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
21295
|
+
"created",
|
|
21296
|
+
"updated",
|
|
21297
|
+
"removed",
|
|
21298
|
+
"archived"
|
|
21299
|
+
]);
|
|
21300
|
+
async function runSetupAgent(targetDir, options) {
|
|
21301
|
+
const saved = loadToolingConfigStrict(targetDir);
|
|
21302
|
+
if (saved === void 0) throw new FatalError(`No .tooling.json in ${targetDir}. Run bst repo:sync first to create one, so the repo's own conventions are chosen before the agent is enabled.`);
|
|
21303
|
+
const previous = previousState(saved);
|
|
21304
|
+
let consent = "already-enabled";
|
|
21305
|
+
if (previous !== "true") {
|
|
21306
|
+
const displayPath = path.join(targetDir, ".tooling.json");
|
|
21307
|
+
let parsedFile;
|
|
21308
|
+
try {
|
|
21309
|
+
parsedFile = JSON.parse(readFileSync(displayPath, "utf8"));
|
|
21310
|
+
} catch (error) {
|
|
21311
|
+
throw new FatalError(`Could not read ${displayPath} as JSON: ${describeError$1(error)}`);
|
|
21312
|
+
}
|
|
21313
|
+
const rawFile = RawToolingConfigSchema.safeParse(parsedFile);
|
|
21314
|
+
if (!rawFile.success) throw new FatalError(`Could not read ${displayPath} as a config object: ${rawFile.error.message}`);
|
|
21315
|
+
const edited = withAgentEnabled(rawFile.data);
|
|
21316
|
+
const content = `${JSON.stringify(edited, null, 2)}\n`;
|
|
21317
|
+
parseToolingConfigStrict(content, displayPath);
|
|
21318
|
+
const context = createContext(mergeWithSavedConfig(buildDefaultConfig(targetDir, {}), saved), async () => "overwrite");
|
|
21319
|
+
context.ctx.write(".tooling.json", context.ctx.format(".tooling.json", content));
|
|
21320
|
+
consent = "written";
|
|
21321
|
+
}
|
|
21322
|
+
if (consent === "written") log$1.success("Set agent.enabled to true in .tooling.json");
|
|
21323
|
+
else log$1.info("agent.enabled is already true");
|
|
21324
|
+
const report = await runUpdateReport(targetDir, {}, { installOutput: options.installOutput });
|
|
21325
|
+
const failed = failedResults(report.results);
|
|
21326
|
+
const changed = report.results.filter((result) => CHANGE_ACTIONS.has(result.action)).map((result) => ({
|
|
21327
|
+
filePath: result.filePath,
|
|
21328
|
+
action: result.action
|
|
21329
|
+
}));
|
|
21330
|
+
let status;
|
|
21331
|
+
if (report.status === "stopped" || failed.length > 0) status = "failed";
|
|
21332
|
+
else if (changed.length > 0) status = "changed";
|
|
21333
|
+
else status = "up-to-date";
|
|
21334
|
+
if (status === "failed") log$1.error("The sync failed. Fix what it reported, then run bst setup:agent again.");
|
|
21335
|
+
else log$1.info("Commit these changes and land them on the default branch; the agent reads consent there within about a minute.");
|
|
21336
|
+
return {
|
|
21337
|
+
dir: targetDir,
|
|
21338
|
+
consent,
|
|
21339
|
+
previous,
|
|
21340
|
+
sync: {
|
|
21341
|
+
status,
|
|
21342
|
+
changed,
|
|
21343
|
+
failed,
|
|
21344
|
+
install: report.install
|
|
21345
|
+
},
|
|
21346
|
+
exitCode: status === "failed" ? 1 : 0
|
|
21347
|
+
};
|
|
21348
|
+
}
|
|
21349
|
+
const setupAgentCommand = defineCommand({
|
|
21350
|
+
meta: {
|
|
21351
|
+
name: "setup:agent",
|
|
21352
|
+
description: "Enable the agent for this repository: set agent.enabled to true in .tooling.json, then run the same sync as repo:sync, which installs @bensandee/agent-cli and generates the files the agent reads. Commits nothing -- land the change on the default branch yourself."
|
|
21353
|
+
},
|
|
21354
|
+
args: {
|
|
21355
|
+
dir: {
|
|
21356
|
+
type: "positional",
|
|
21357
|
+
valueHint: "dir",
|
|
21358
|
+
description: "Directory to enable (default: current directory)",
|
|
21359
|
+
required: false
|
|
21360
|
+
},
|
|
21361
|
+
json: {
|
|
21362
|
+
type: "boolean",
|
|
21363
|
+
description: "Output the report as JSON: dir, consent, previous, sync { status, changed, failed, install }, exitCode"
|
|
21364
|
+
}
|
|
21365
|
+
},
|
|
21366
|
+
async run({ args }) {
|
|
21367
|
+
const report = await runSetupAgent(path.resolve(args.dir ?? "."), { installOutput: args.json === true || isJsonMode() ? "capture" : "inherit" });
|
|
21368
|
+
createCliOutput(args.json === true).json(report);
|
|
21369
|
+
process.exitCode = report.exitCode;
|
|
21370
|
+
}
|
|
21371
|
+
});
|
|
21372
|
+
//#endregion
|
|
20817
21373
|
//#region src/commands/setup-secrets.ts
|
|
20818
21374
|
function isCancelled(value) {
|
|
20819
21375
|
return clack.isCancel(value);
|
|
@@ -21758,6 +22314,7 @@ const coverageRecordCommand = defineCommand({
|
|
|
21758
22314
|
});
|
|
21759
22315
|
return;
|
|
21760
22316
|
}
|
|
22317
|
+
refuseForgeDefaultMismatch(readConfiguredDefaultBranch(targetDir), processCiEventSource(), "coverage:record");
|
|
21761
22318
|
const summaryPath = path.join(targetDir, COVERAGE_SUMMARY_FILE);
|
|
21762
22319
|
if (!existsSync(summaryPath)) throw new FatalError(`${COVERAGE_SUMMARY_FILE} not found. Run \`pnpm test:coverage\` before \`coverage:record\`.`);
|
|
21763
22320
|
const summary = parseV8Summary(readFileSync(summaryPath, "utf-8"));
|
|
@@ -23230,6 +23787,7 @@ const healthRecordCommand = defineCommand({
|
|
|
23230
23787
|
});
|
|
23231
23788
|
return;
|
|
23232
23789
|
}
|
|
23790
|
+
refuseForgeDefaultMismatch(readConfiguredDefaultBranch(targetDir), processCiEventSource(), "health:record");
|
|
23233
23791
|
const executor = createRealHealthExecutor(targetDir);
|
|
23234
23792
|
const headResult = executor.exec("git", ["rev-parse", "HEAD"], { cwd: targetDir });
|
|
23235
23793
|
if (headResult.exitCode !== 0) throw new FatalError(`Failed to resolve HEAD sha: ${headResult.stderr.trim()}`);
|
|
@@ -24093,7 +24651,7 @@ function collectViolations(exec, config, opts) {
|
|
|
24093
24651
|
retiredCount += result.retired;
|
|
24094
24652
|
}
|
|
24095
24653
|
violations.push(...collectSpecRetirementViolations(exec, pairs, config, opts));
|
|
24096
|
-
const activeDirs =
|
|
24654
|
+
const activeDirs = matchableActiveDirs(exec, config);
|
|
24097
24655
|
const unmatchedEnforced = Array.isArray(config.enforced) ? config.enforced.filter((entry) => !activeDirs.includes(entry)) : [];
|
|
24098
24656
|
return {
|
|
24099
24657
|
violations,
|
|
@@ -24236,7 +24794,7 @@ const docsCheckCommand = defineCommand({
|
|
|
24236
24794
|
},
|
|
24237
24795
|
json: {
|
|
24238
24796
|
type: "boolean",
|
|
24239
|
-
description: "Emit one JSON document: violations ([{file, rule, message, fix, severity (fatal | advisory)}]), activeCount, retiredCount, pairCount, pass, activeDirs, unmatchedEnforced. When docs are disabled in .tooling.json the document is only enabled (false)."
|
|
24797
|
+
description: "Emit one JSON document: violations ([{file, rule, message, fix, severity (fatal | advisory)}]), activeCount, retiredCount, pairCount, pass, activeDirs, unmatchedEnforced. activeDirs lists every existing doc directory an enforced entry can match, holding docs or not; unmatchedEnforced the .tooling.json#docs.enforced entries that match none of them. When docs are disabled in .tooling.json the document is only enabled (false)."
|
|
24240
24798
|
},
|
|
24241
24799
|
verbose: {
|
|
24242
24800
|
type: "boolean",
|
|
@@ -25581,58 +26139,6 @@ const issueDownloadCommand = defineCommand({
|
|
|
25581
26139
|
}
|
|
25582
26140
|
});
|
|
25583
26141
|
//#endregion
|
|
25584
|
-
//#region ../forgejo/src/issue-labels.ts
|
|
25585
|
-
const TimelineEntrySchema = z.object({
|
|
25586
|
-
id: z.number(),
|
|
25587
|
-
type: z.string(),
|
|
25588
|
-
body: z.string().nullish(),
|
|
25589
|
-
created_at: z.string(),
|
|
25590
|
-
label: z.object({ name: z.string() }).nullish(),
|
|
25591
|
-
user: z.object({ login: z.string() }).nullish()
|
|
25592
|
-
});
|
|
25593
|
-
z.array(TimelineEntrySchema).nullable();
|
|
25594
|
-
async function resolveLabelId(executor, conn, name, labelIds) {
|
|
25595
|
-
const cached = labelIds?.get(name);
|
|
25596
|
-
if (cached !== void 0) return cached;
|
|
25597
|
-
const [id] = await resolveLabelIds(executor, conn, [name]);
|
|
25598
|
-
if (id === void 0) throw new UnexpectedError(`Label id resolution returned nothing for "${name}"`);
|
|
25599
|
-
return id;
|
|
25600
|
-
}
|
|
25601
|
-
async function addForgejoIssueLabel(executor, conn, number, name, labelIds) {
|
|
25602
|
-
const id = await resolveLabelId(executor, conn, name, labelIds);
|
|
25603
|
-
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels`, {
|
|
25604
|
-
method: "POST",
|
|
25605
|
-
headers: jsonHeaders(conn),
|
|
25606
|
-
body: JSON.stringify({ labels: [id] })
|
|
25607
|
-
}, { idempotent: true });
|
|
25608
|
-
if (!res.ok) await throwForgejoFailure(res, {
|
|
25609
|
-
verb: "add label",
|
|
25610
|
-
resource: `#${String(number)}`
|
|
25611
|
-
}, issueNotFoundMessage(conn, number));
|
|
25612
|
-
}
|
|
25613
|
-
async function removeForgejoIssueLabel(executor, conn, number, name, labelIds) {
|
|
25614
|
-
const id = await resolveLabelId(executor, conn, name, labelIds);
|
|
25615
|
-
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels/${String(id)}`, {
|
|
25616
|
-
method: "DELETE",
|
|
25617
|
-
headers: authHeaders$2(conn)
|
|
25618
|
-
}, { idempotent: true });
|
|
25619
|
-
if (!res.ok) await throwForgejoFailure(res, {
|
|
25620
|
-
verb: "remove label",
|
|
25621
|
-
resource: `#${String(number)}`
|
|
25622
|
-
}, issueNotFoundMessage(conn, number));
|
|
25623
|
-
}
|
|
25624
|
-
async function replaceForgejoIssueLabels(executor, conn, number, labelIds) {
|
|
25625
|
-
const res = await executor.fetch(`${issuesBase(conn)}/${String(number)}/labels`, {
|
|
25626
|
-
method: "PUT",
|
|
25627
|
-
headers: jsonHeaders(conn),
|
|
25628
|
-
body: JSON.stringify({ labels: labelIds })
|
|
25629
|
-
}, { idempotent: true });
|
|
25630
|
-
if (!res.ok) await throwForgejoFailure(res, {
|
|
25631
|
-
verb: "replace labels",
|
|
25632
|
-
resource: `#${String(number)}`
|
|
25633
|
-
}, issueNotFoundMessage(conn, number));
|
|
25634
|
-
}
|
|
25635
|
-
//#endregion
|
|
25636
26142
|
//#region ../forgejo/src/issue-run-labels.ts
|
|
25637
26143
|
async function labelIssue(resolve, request) {
|
|
25638
26144
|
const ref = parseIssueRef(request.ref);
|
|
@@ -25871,6 +26377,9 @@ var EmptyCommentError = class extends FatalError {
|
|
|
25871
26377
|
super("The comment resolved to empty text.");
|
|
25872
26378
|
}
|
|
25873
26379
|
};
|
|
26380
|
+
function rejectEmptyComment(comment) {
|
|
26381
|
+
if (comment !== void 0 && comment.trim() === "") throw new EmptyCommentError();
|
|
26382
|
+
}
|
|
25874
26383
|
var PullRequestStateError = class extends FatalError {
|
|
25875
26384
|
number;
|
|
25876
26385
|
action;
|
|
@@ -25880,20 +26389,35 @@ var PullRequestStateError = class extends FatalError {
|
|
|
25880
26389
|
this.action = action;
|
|
25881
26390
|
}
|
|
25882
26391
|
};
|
|
26392
|
+
var NotAPullRequestError = class extends FatalError {
|
|
26393
|
+
number;
|
|
26394
|
+
constructor(number) {
|
|
26395
|
+
super(`#${String(number)} is an issue, not a pull request.`);
|
|
26396
|
+
this.number = number;
|
|
26397
|
+
}
|
|
26398
|
+
};
|
|
26399
|
+
var StateNotTakenError = class extends FatalError {
|
|
26400
|
+
number;
|
|
26401
|
+
expected;
|
|
26402
|
+
actual;
|
|
26403
|
+
constructor(number, expected, actual) {
|
|
26404
|
+
super(`The forge did not change #${String(number)}: it is still ${actual}, not ${expected}.`);
|
|
26405
|
+
this.number = number;
|
|
26406
|
+
this.expected = expected;
|
|
26407
|
+
this.actual = actual;
|
|
26408
|
+
}
|
|
26409
|
+
};
|
|
25883
26410
|
const TARGET_STATE = {
|
|
25884
26411
|
close: "closed",
|
|
25885
26412
|
reopen: "open"
|
|
25886
26413
|
};
|
|
25887
|
-
async function
|
|
25888
|
-
const ref = parseIssueRef(request.ref);
|
|
25889
|
-
const number = requireIssueNumber(ref);
|
|
25890
|
-
if (request.comment !== void 0 && request.comment.trim() === "") throw new EmptyCommentError();
|
|
25891
|
-
const commentGiven = request.comment !== void 0;
|
|
25892
|
-
const { api } = resolve(resolveIssueTarget(ref.target, request.repo));
|
|
26414
|
+
async function changeState(api, number, request, subject) {
|
|
25893
26415
|
const before = await api.get(number);
|
|
25894
|
-
if (before.isPullRequest) throw new PullRequestStateError(number, request.action);
|
|
26416
|
+
if (subject === "issue" && before.isPullRequest) throw new PullRequestStateError(number, request.action);
|
|
26417
|
+
if (subject === "pull-request" && !before.isPullRequest) throw new NotAPullRequestError(number);
|
|
26418
|
+
const commentGiven = request.comment !== void 0;
|
|
25895
26419
|
const targetState = TARGET_STATE[request.action];
|
|
25896
|
-
const buildResult = (url, current, changed, posted) => ({
|
|
26420
|
+
const buildResult = (url, current, changed, posted, merged) => ({
|
|
25897
26421
|
number,
|
|
25898
26422
|
url,
|
|
25899
26423
|
action: request.action,
|
|
@@ -25902,20 +26426,32 @@ async function setIssueState(resolve, request) {
|
|
|
25902
26426
|
current
|
|
25903
26427
|
},
|
|
25904
26428
|
changed,
|
|
25905
|
-
...commentGiven ? { comment: { posted } } : {}
|
|
26429
|
+
...commentGiven ? { comment: { posted } } : {},
|
|
26430
|
+
...merged === void 0 ? {} : { merged }
|
|
25906
26431
|
});
|
|
25907
|
-
if (before.state === targetState)
|
|
26432
|
+
if (before.state === targetState) {
|
|
26433
|
+
const merged = subject === "pull-request" ? (await api.pullRequest(number)).merged : void 0;
|
|
26434
|
+
return buildResult(before.url, before.state, false, false, merged);
|
|
26435
|
+
}
|
|
25908
26436
|
if (request.dryRun) return buildResult(before.url, targetState, true, true);
|
|
25909
26437
|
if (commentGiven) await api.addComment(number, request.comment ?? "");
|
|
25910
26438
|
let after;
|
|
25911
26439
|
try {
|
|
25912
26440
|
after = await api.setState(number, targetState);
|
|
26441
|
+
if (after.state !== targetState) throw new StateNotTakenError(number, targetState, after.state);
|
|
25913
26442
|
} catch (error) {
|
|
25914
26443
|
if (commentGiven && error instanceof Error) error.message = `The comment on #${String(number)} was already posted; a retry will duplicate it. ` + error.message;
|
|
25915
26444
|
throw error;
|
|
25916
26445
|
}
|
|
25917
26446
|
return buildResult(after.url, after.state, true, true);
|
|
25918
26447
|
}
|
|
26448
|
+
async function setIssueState(resolve, request) {
|
|
26449
|
+
const ref = parseIssueRef(request.ref);
|
|
26450
|
+
const number = requireIssueNumber(ref);
|
|
26451
|
+
rejectEmptyComment(request.comment);
|
|
26452
|
+
const { api } = resolve(resolveIssueTarget(ref.target, request.repo));
|
|
26453
|
+
return changeState(api, number, request, "issue");
|
|
26454
|
+
}
|
|
25919
26455
|
const PAST_TENSE = {
|
|
25920
26456
|
close: "Closed",
|
|
25921
26457
|
reopen: "Reopened"
|
|
@@ -26808,7 +27344,7 @@ async function runCli(config) {
|
|
|
26808
27344
|
//#region src/cli-main.ts
|
|
26809
27345
|
setLogLevel(resolveLogLevel({}));
|
|
26810
27346
|
setForgejoLogger(log$1);
|
|
26811
|
-
const displayVersion = "0.
|
|
27347
|
+
const displayVersion = "0.91.0";
|
|
26812
27348
|
const bstSubCommands = {
|
|
26813
27349
|
"repo:sync": syncCommand,
|
|
26814
27350
|
"checks:run": runChecksCommand,
|
|
@@ -26831,6 +27367,7 @@ const bstSubCommands = {
|
|
|
26831
27367
|
"hooks:audit": hooksAuditCommand,
|
|
26832
27368
|
"hooks:sync": hooksSyncCommand,
|
|
26833
27369
|
"setup:secrets": setupSecretsCommand,
|
|
27370
|
+
"setup:agent": setupAgentCommand,
|
|
26834
27371
|
"webhook:send": webhookSendCommand,
|
|
26835
27372
|
"coverage:summary": coverageSummaryCommand,
|
|
26836
27373
|
"coverage:check": coverageCheckCommand,
|
|
@@ -26864,7 +27401,8 @@ const bstSubCommands = {
|
|
|
26864
27401
|
"plan:brief": planBriefCommand,
|
|
26865
27402
|
"plan:review-package": planReviewPackageCommand,
|
|
26866
27403
|
"plan:status": planStatusCommand,
|
|
26867
|
-
"work:status": workStatusCommand
|
|
27404
|
+
"work:status": workStatusCommand,
|
|
27405
|
+
"work:start": workStartCommand
|
|
26868
27406
|
};
|
|
26869
27407
|
async function runBstCli(extraSubCommands = {}) {
|
|
26870
27408
|
await runCli({
|