@bensandee/tooling 0.79.0 → 0.80.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/dist/bin.mjs +122 -41
- package/package.json +1 -1
- package/tooling.schema.json +4 -0
package/dist/bin.mjs
CHANGED
|
@@ -397,7 +397,7 @@ function loadEnvFileForBin(rawArgs, io = processIo) {
|
|
|
397
397
|
if (resolveJsonMode(rawArgs)) {
|
|
398
398
|
const document = buildErrorDocument({
|
|
399
399
|
command: resolveCommandName(rawArgs),
|
|
400
|
-
version: "0.
|
|
400
|
+
version: "0.80.0",
|
|
401
401
|
kind: "usage",
|
|
402
402
|
message
|
|
403
403
|
});
|
|
@@ -764,6 +764,11 @@ const AsciiConfigSchema = z.strictObject({
|
|
|
764
764
|
treatAsProse: z.array(z.string()).optional().meta({ description: "Path globs (matched the same way as exclude) whose files --fix classifies as prose wholesale regardless of extension, flattening rather than flagging. The opt-back-in for an extension with no dedicated lexical-context classifier where blind flattening is known safe. No built-in defaults." })
|
|
765
765
|
});
|
|
766
766
|
const LintConfigSchema = z.strictObject({ strictTypeAware: z.boolean().optional().meta({ description: "Make `bst lint:types` (the type-aware oxlint gate, backed by oxlint-tsgolint) exit non-zero on any finding while the tooling is still in its report phase (default false; the enforce phase, a later tooling release, exits non-zero unconditionally). A missing oxlint-tsgolint binary is also a hard failure rather than a notice while this is set: arming the gate is what turns a platform gap into a build break, deliberately, because the repo that armed it is the one that asked for it." }) });
|
|
767
|
+
const ChecksConfigSchema = z.strictObject({
|
|
768
|
+
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." }),
|
|
769
|
+
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." }),
|
|
770
|
+
runOnVersionPr: z.boolean().optional().meta({ description: "Run the CI check job on the changesets version PR (changeset-release/main). Default false: the generated check job is skipped there, because that branch is regenerated on every push to main and main's own run gates the release. Has no effect unless releaseStrategy is changesets." })
|
|
771
|
+
});
|
|
767
772
|
const CoverageConfigSchema = z.strictObject({
|
|
768
773
|
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." }),
|
|
769
774
|
historyBranch: z.string().optional().meta({ description: "Orphan branch name for coverage history (default: 'bst/coverage-metrics')" }),
|
|
@@ -1892,10 +1897,6 @@ const BranchPruneConfigSchema = z.strictObject({
|
|
|
1892
1897
|
exclude: z.array(z.string()).optional().meta({ description: "Branch-name globs added to bst branch:prune's built-in never-touch list (main, changeset-release/*, renovate/*, bst/coverage-metrics, bst/health-metrics, bst/egress-metrics, anything with an open PR or protected). Additive only -- there is no key that removes a built-in entry." })
|
|
1893
1898
|
});
|
|
1894
1899
|
const IssuesConfigSchema = z.strictObject({ assignees: z.array(z.string().min(1)).optional().meta({ description: "The repo's default reviewer(s) for issues opened by `bst issue:create` -- assigned when the invocation names no --assignee and no --repo. An explicit --assignee replaces this default rather than adding to it; --no-assignee opens an issue deliberately unassigned." }) });
|
|
1895
|
-
const ChecksConfigSchema = z.strictObject({
|
|
1896
|
-
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." }),
|
|
1897
|
-
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." })
|
|
1898
|
-
});
|
|
1899
1900
|
/** One label definition inside a `labelSets` entry -- the same shape `bst forgejo:labels sync`
|
|
1900
1901
|
* writes to the forge (`AgentLabelSpec` in `@bensandee/forgejo/label-provisioning`), expressed as a
|
|
1901
1902
|
* zod schema so a malformed entry in `.tooling.json` fails at load time rather than at the first API
|
|
@@ -5225,6 +5226,7 @@ function buildWorkflowYaml(options) {
|
|
|
5225
5226
|
if (options.permissions) doc["permissions"] = options.permissions;
|
|
5226
5227
|
if (options.concurrency) doc["concurrency"] = options.concurrency;
|
|
5227
5228
|
const jobs = { [options.jobName]: {
|
|
5229
|
+
...options.jobIf && { if: options.jobIf },
|
|
5228
5230
|
"runs-on": options.runsOn ?? "ubuntu-latest",
|
|
5229
5231
|
...options.jobEnv && { env: options.jobEnv },
|
|
5230
5232
|
...options.jobOutputs && { outputs: options.jobOutputs },
|
|
@@ -6712,7 +6714,7 @@ function buildManagedDevDeps(ctx) {
|
|
|
6712
6714
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
6713
6715
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
6714
6716
|
if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.5";
|
|
6715
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.
|
|
6717
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.80.0";
|
|
6716
6718
|
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.15.2";
|
|
6717
6719
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
6718
6720
|
"@changesets/cli": "3.0.3",
|
|
@@ -9182,6 +9184,16 @@ async function generateReleaseCi(ctx) {
|
|
|
9182
9184
|
};
|
|
9183
9185
|
}
|
|
9184
9186
|
//#endregion
|
|
9187
|
+
//#region src/release/version-branch.ts
|
|
9188
|
+
/**
|
|
9189
|
+
* The branch the changesets release flow writes its version commit to, and so the head branch of
|
|
9190
|
+
* the version PR. `bst release:changesets` force-pushes it on Forgejo; on GitHub it is
|
|
9191
|
+
* `changesets/action@v1`'s default, `changeset-release/<base>`. Not configurable. The CI generator
|
|
9192
|
+
* reads it too, to skip the check job on that PR, which is why it lives at `release/`'s root
|
|
9193
|
+
* rather than in `publish/`.
|
|
9194
|
+
*/
|
|
9195
|
+
const VERSION_PR_BRANCH = "changeset-release/main";
|
|
9196
|
+
//#endregion
|
|
9185
9197
|
//#region src/generators/ci.ts
|
|
9186
9198
|
function coverageHistorySteps() {
|
|
9187
9199
|
return [
|
|
@@ -9381,6 +9393,8 @@ async function generateCi(ctx) {
|
|
|
9381
9393
|
...emitCoverageSteps || emitHealthHistory ? IS_MAIN_JOB_ENV : {},
|
|
9382
9394
|
...npmAuth.jobEnv
|
|
9383
9395
|
};
|
|
9396
|
+
const checksSlice = parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ checks: ChecksConfigSchema.optional() }), { block: "checks" });
|
|
9397
|
+
const skipOnVersionPr = isChangesets && checksSlice?.checks?.runOnVersionPr !== true;
|
|
9384
9398
|
const content = buildWorkflowYaml({
|
|
9385
9399
|
ci: ctx.config.ci,
|
|
9386
9400
|
name: "CI",
|
|
@@ -9395,6 +9409,7 @@ async function generateCi(ctx) {
|
|
|
9395
9409
|
enableEmailNotifications: isForgejo,
|
|
9396
9410
|
...isChangesets && { concurrency: ciConcurrency(ctx.config.ci) },
|
|
9397
9411
|
jobName: "check",
|
|
9412
|
+
...skipOnVersionPr && { jobIf: `github.head_ref != 'changeset-release/main'` },
|
|
9398
9413
|
...Object.keys(checkJobEnv).length > 0 && { jobEnv: checkJobEnv },
|
|
9399
9414
|
...jobOutputs && { jobOutputs },
|
|
9400
9415
|
steps,
|
|
@@ -10660,7 +10675,11 @@ const GIT_GUIDE_BODY = [
|
|
|
10660
10675
|
"",
|
|
10661
10676
|
"## Commit subjects",
|
|
10662
10677
|
"",
|
|
10663
|
-
"Follow Conventional Commits: `<type>(<scope>): <summary>`. The type is one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `perf`, `build`, `ci`; the scope is optional and names the package or area touched; the summary is an imperative, plain-language sentence. This binds a commit message you suggest to the user too. Nothing checks it, so do not copy the style of recent `git log` if it has drifted."
|
|
10678
|
+
"Follow Conventional Commits: `<type>(<scope>): <summary>`. The type is one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `perf`, `build`, `ci`; the scope is optional and names the package or area touched; the summary is an imperative, plain-language sentence. This binds a commit message you suggest to the user too. Nothing checks it, so do not copy the style of recent `git log` if it has drifted.",
|
|
10679
|
+
"",
|
|
10680
|
+
"## Pull request titles",
|
|
10681
|
+
"",
|
|
10682
|
+
"Start a pull request's title with the stage it carries: `Spec:` for a spec, `Plan:` for a plan, and no stem once it carries the implementation. A draft keeps its `WIP:` marker in front (`WIP: Plan: Add retry to uploads`). When a pull request moves on to the next stage, move the stem with it. This is informational -- nothing checks it and nothing fails without it -- but it tells a reviewer at a glance what they are reading."
|
|
10664
10683
|
].join("\n");
|
|
10665
10684
|
/**
|
|
10666
10685
|
* The plan lifecycle. `bst docs:check` owns the mechanics; this states the one rule it cannot
|
|
@@ -11324,7 +11343,7 @@ function reportFailures(failed, command) {
|
|
|
11324
11343
|
function generateMigratePrompt(results, config, detected) {
|
|
11325
11344
|
const sections = [];
|
|
11326
11345
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11327
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
11346
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.80.0 repo:sync\` on ${timestamp}_\n`);
|
|
11328
11347
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
11329
11348
|
if (changed.length > 0) {
|
|
11330
11349
|
const items = changed.map((r) => {
|
|
@@ -12350,6 +12369,32 @@ function execStep(cmd, cwd, sink = process.stdout) {
|
|
|
12350
12369
|
});
|
|
12351
12370
|
}
|
|
12352
12371
|
//#endregion
|
|
12372
|
+
//#region src/checks/skip-report.ts
|
|
12373
|
+
function matches(name, patterns) {
|
|
12374
|
+
return patterns.size > 0 && picomatch.isMatch(name, [...patterns]);
|
|
12375
|
+
}
|
|
12376
|
+
/**
|
|
12377
|
+
* Which steps each skip source removed, for reporting. Only a step with a defined script is
|
|
12378
|
+
* named: one without a script was never going to run, so a repo with no Docker is never told
|
|
12379
|
+
* `docker:build` was skipped. A step matched by both sources is credited to `--skip`, the one
|
|
12380
|
+
* the caller typed.
|
|
12381
|
+
*/
|
|
12382
|
+
function groupSkippedSteps(steps, defined, flagSkip, configSkip) {
|
|
12383
|
+
const bySource = {
|
|
12384
|
+
"--skip": [],
|
|
12385
|
+
"checks.skip": []
|
|
12386
|
+
};
|
|
12387
|
+
for (const name of steps) {
|
|
12388
|
+
if (!defined.has(name)) continue;
|
|
12389
|
+
if (matches(name, flagSkip)) bySource["--skip"].push(name);
|
|
12390
|
+
else if (matches(name, configSkip)) bySource["checks.skip"].push(name);
|
|
12391
|
+
}
|
|
12392
|
+
return ["--skip", "checks.skip"].filter((source) => bySource[source].length > 0).map((source) => ({
|
|
12393
|
+
source,
|
|
12394
|
+
steps: bySource[source]
|
|
12395
|
+
}));
|
|
12396
|
+
}
|
|
12397
|
+
//#endregion
|
|
12353
12398
|
//#region src/utils/branch.ts
|
|
12354
12399
|
const BRANCH_REF_PREFIX = "refs/heads/";
|
|
12355
12400
|
function defaultGitRunner(args, cwd) {
|
|
@@ -12601,8 +12646,20 @@ function defaultGetScripts(targetDir) {
|
|
|
12601
12646
|
}
|
|
12602
12647
|
}
|
|
12603
12648
|
const rawLog$2 = (msg) => console.log(msg);
|
|
12604
|
-
|
|
12605
|
-
|
|
12649
|
+
/**
|
|
12650
|
+
* The skipped names on a failing run, as a line of their own. Never folded into the
|
|
12651
|
+
* `Failed checks: <names>` line: the agent's verify backstop (`parseFailingStep`) reads
|
|
12652
|
+
* everything after it as step names and discards the lot on anything else.
|
|
12653
|
+
*/
|
|
12654
|
+
function failedRunSkippedLine(skipped) {
|
|
12655
|
+
return skipped.length === 0 ? null : `Skipped: ${skipped.join(", ")}`;
|
|
12656
|
+
}
|
|
12657
|
+
function passedSummary(warned, skipped) {
|
|
12658
|
+
const parts = [...warned.length > 0 ? [`${String(warned.length)} advisory: ${warned.map((w) => w.name).join(", ")}`] : [], ...skipped.length > 0 ? [`skipped: ${skipped.join(", ")}`] : []];
|
|
12659
|
+
return parts.length === 0 ? "All checks passed" : `All checks passed (${parts.join("; ")})`;
|
|
12660
|
+
}
|
|
12661
|
+
function skippedLine(group) {
|
|
12662
|
+
return `Skipped (${group.source}): ${group.steps.join(", ")}`;
|
|
12606
12663
|
}
|
|
12607
12664
|
const ciReporter = {
|
|
12608
12665
|
groupStart: (name) => rawLog$2(`::group::${name}`),
|
|
@@ -12617,9 +12674,16 @@ const ciReporter = {
|
|
|
12617
12674
|
rawLog$2(`::error::${name} failed`);
|
|
12618
12675
|
},
|
|
12619
12676
|
undefinedCheck: (name) => rawLog$2(`::error::${name} not defined in package.json`),
|
|
12677
|
+
skipped: (groups) => {
|
|
12678
|
+
for (const group of groups) rawLog$2(skippedLine(group));
|
|
12679
|
+
},
|
|
12620
12680
|
skippedNotDefined: (names) => rawLog$2(`Skipped (not defined): ${names.join(", ")}`),
|
|
12621
|
-
allPassed: (warned) => rawLog$2(
|
|
12622
|
-
anyFailed: (names) =>
|
|
12681
|
+
allPassed: (warned, skipped) => rawLog$2(`[ok] ${passedSummary(warned, skipped)}`),
|
|
12682
|
+
anyFailed: (names, skipped) => {
|
|
12683
|
+
const skippedNames = failedRunSkippedLine(skipped);
|
|
12684
|
+
if (skippedNames !== null) rawLog$2(skippedNames);
|
|
12685
|
+
rawLog$2(`::error::Failed checks: ${names.join(", ")}`);
|
|
12686
|
+
}
|
|
12623
12687
|
};
|
|
12624
12688
|
const localReporter = {
|
|
12625
12689
|
groupStart: (_name) => {},
|
|
@@ -12628,16 +12692,35 @@ const localReporter = {
|
|
|
12628
12692
|
warned: (name, _elapsedS, count) => log$1.warn(`${name}: ${String(count)} advisory finding(s)`),
|
|
12629
12693
|
failed: (name) => log$1.error(`${name} failed`),
|
|
12630
12694
|
undefinedCheck: (name) => log$1.error(`${name} not defined in package.json`),
|
|
12695
|
+
skipped: (groups) => {
|
|
12696
|
+
for (const group of groups) log$1.info(skippedLine(group));
|
|
12697
|
+
},
|
|
12631
12698
|
skippedNotDefined: (names) => log$1.info(`Skipped (not defined): ${names.join(", ")}`),
|
|
12632
|
-
allPassed: (warned) =>
|
|
12633
|
-
anyFailed: (names) =>
|
|
12699
|
+
allPassed: (warned, skipped) => log$1.success(passedSummary(warned, skipped)),
|
|
12700
|
+
anyFailed: (names, skipped) => {
|
|
12701
|
+
const skippedNames = failedRunSkippedLine(skipped);
|
|
12702
|
+
if (skippedNames !== null) log$1.info(skippedNames);
|
|
12703
|
+
log$1.error(`Failed checks: ${names.join(", ")}`);
|
|
12704
|
+
}
|
|
12634
12705
|
};
|
|
12706
|
+
/**
|
|
12707
|
+
* Name every skipped step before the first one runs, and return the names for the summary: a
|
|
12708
|
+
* step that silently vanishes from the run reads as a bug in the pipeline rather than a skip
|
|
12709
|
+
* someone asked for.
|
|
12710
|
+
*/
|
|
12711
|
+
function reportSkipped(reporter, names, defined, flagSkip, configSkip) {
|
|
12712
|
+
const groups = groupSkippedSteps(names, defined, flagSkip, configSkip);
|
|
12713
|
+
reporter.skipped(groups);
|
|
12714
|
+
return groups.flatMap((group) => group.steps);
|
|
12715
|
+
}
|
|
12635
12716
|
async function runRunChecks(targetDir, options = {}) {
|
|
12636
12717
|
const exec = options.execCommand ?? execStep;
|
|
12637
12718
|
const getScripts = options.getScripts ?? defaultGetScripts;
|
|
12638
12719
|
const toolingConfig = options.toolingConfig ?? loadToolingConfig(targetDir);
|
|
12639
12720
|
const configChecks = toolingConfig?.checks;
|
|
12640
|
-
const
|
|
12721
|
+
const flagSkip = new Set(options.skip ?? []);
|
|
12722
|
+
const configSkip = new Set(configChecks?.skip ?? []);
|
|
12723
|
+
const skip = /* @__PURE__ */ new Set([...flagSkip, ...configSkip]);
|
|
12641
12724
|
const add = [.../* @__PURE__ */ new Set([...options.add ?? [], ...configChecks?.add ?? []])];
|
|
12642
12725
|
const isCI = Boolean(process.env["CI"]);
|
|
12643
12726
|
const failFast = options.failFast ?? !isCI;
|
|
@@ -12656,6 +12739,7 @@ async function runRunChecks(targetDir, options = {}) {
|
|
|
12656
12739
|
if (coverage.enabled && !useCoverageScript && coverage.source === "flag") log$1.warn("--with-coverage requested but test:coverage script is not defined; falling back to test");
|
|
12657
12740
|
if (coverage.enabled && !useCoverageScript && coverage.source === "config") log$1.warn("coverage.gateOnPullRequests is set but test:coverage script is not defined; falling back to test");
|
|
12658
12741
|
debug(vc, `Coverage: ${options.coverage ?? "auto"} (source: ${coverage.source}) -> ${useCoverageScript ? "test:coverage" : "test"}`);
|
|
12742
|
+
const skippedSteps = reportSkipped(reporter, allChecks.map((check) => check.name), definedScripts, flagSkip, configSkip);
|
|
12659
12743
|
for (const check of allChecks) {
|
|
12660
12744
|
if (shouldSkip(check.name, skip)) continue;
|
|
12661
12745
|
if (!definedScripts.has(check.name)) {
|
|
@@ -12694,10 +12778,10 @@ async function runRunChecks(targetDir, options = {}) {
|
|
|
12694
12778
|
}
|
|
12695
12779
|
if (notDefined.length > 0) reporter.skippedNotDefined(notDefined);
|
|
12696
12780
|
if (failures.length > 0) {
|
|
12697
|
-
reporter.anyFailed(failures);
|
|
12781
|
+
reporter.anyFailed(failures, skippedSteps);
|
|
12698
12782
|
return 1;
|
|
12699
12783
|
}
|
|
12700
|
-
reporter.allPassed(warnedSteps);
|
|
12784
|
+
reporter.allPassed(warnedSteps, skippedSteps);
|
|
12701
12785
|
return 0;
|
|
12702
12786
|
}
|
|
12703
12787
|
/**
|
|
@@ -13733,9 +13817,8 @@ function childOutput(result) {
|
|
|
13733
13817
|
}
|
|
13734
13818
|
//#endregion
|
|
13735
13819
|
//#region src/release/publish/version.ts
|
|
13736
|
-
const BRANCH = "changeset-release/main";
|
|
13737
13820
|
const MAIN_REF = "refs/heads/main";
|
|
13738
|
-
const BRANCH_REF = `refs/heads/${
|
|
13821
|
+
const BRANCH_REF = `refs/heads/${VERSION_PR_BRANCH}`;
|
|
13739
13822
|
/**
|
|
13740
13823
|
* Read one remote ref's SHA, without mutating the local repository or its remote-tracking refs.
|
|
13741
13824
|
*
|
|
@@ -13788,7 +13871,7 @@ function isSuperseded(executor, config, auth, baseSha) {
|
|
|
13788
13871
|
return false;
|
|
13789
13872
|
}
|
|
13790
13873
|
if (remoteMain === baseSha) return false;
|
|
13791
|
-
log$1.warn(`Superseded release run: this job versioned ${baseSha}, but main has advanced to ${remoteMain}. A newer run is versioning a superset of these changesets -- skipping to avoid overwriting its version commit on ${
|
|
13874
|
+
log$1.warn(`Superseded release run: this job versioned ${baseSha}, but main has advanced to ${remoteMain}. A newer run is versioning a superset of these changesets -- skipping to avoid overwriting its version commit on ${VERSION_PR_BRANCH}.`);
|
|
13792
13875
|
return true;
|
|
13793
13876
|
}
|
|
13794
13877
|
/** Pattern matching a dateless version heading like `## 1.2.3` or `## 1.2.3-beta.0`. */
|
|
@@ -13895,9 +13978,9 @@ async function runVersionMode(executor, config) {
|
|
|
13895
13978
|
const baseShaResult = executor.exec("git", ["rev-parse", "HEAD"], { cwd: config.cwd });
|
|
13896
13979
|
const baseSha = baseShaResult.exitCode === 0 ? baseShaResult.stdout.trim() : "";
|
|
13897
13980
|
const leaseTip = config.dryRun ? null : readRemoteRef(executor, config, auth, BRANCH_REF);
|
|
13898
|
-
if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${
|
|
13981
|
+
if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${VERSION_PR_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.`);
|
|
13899
13982
|
debug(config, `Base commit: ${baseSha || "(unknown)"}`);
|
|
13900
|
-
debug(config, `Lease tip for ${
|
|
13983
|
+
debug(config, `Lease tip for ${VERSION_PR_BRANCH}: ${leaseTip === null ? "(dry-run)" : leaseTip || "(absent)"}`);
|
|
13901
13984
|
const packagesBefore = executor.listWorkspacePackages(config.cwd);
|
|
13902
13985
|
debug(config, `Packages before versioning: ${packagesBefore.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "(none)"}`);
|
|
13903
13986
|
const changesetConfigPath = path.join(config.cwd, ".changeset", "config.json");
|
|
@@ -13959,7 +14042,7 @@ async function runVersionMode(executor, config) {
|
|
|
13959
14042
|
pr: "none"
|
|
13960
14043
|
};
|
|
13961
14044
|
if (leaseTip === null) throw new UnexpectedError("Push lease was never observed on a non-dry-run release");
|
|
13962
|
-
debug(config, `Force-pushing version commit to ${
|
|
14045
|
+
debug(config, `Force-pushing version commit to ${VERSION_PR_BRANCH} (lease: ${leaseTip || "(absent)"})`);
|
|
13963
14046
|
const pushResult = executor.exec("git", [
|
|
13964
14047
|
...auth.args,
|
|
13965
14048
|
"push",
|
|
@@ -13968,18 +14051,18 @@ async function runVersionMode(executor, config) {
|
|
|
13968
14051
|
`--force-with-lease=${BRANCH_REF}:${leaseTip}`
|
|
13969
14052
|
], { cwd: config.cwd });
|
|
13970
14053
|
debugExec(config, "git push", pushResult);
|
|
13971
|
-
if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${
|
|
14054
|
+
if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${VERSION_PR_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}`);
|
|
13972
14055
|
const conn = {
|
|
13973
14056
|
serverUrl: config.serverUrl,
|
|
13974
14057
|
repository: config.repository,
|
|
13975
14058
|
token: config.token
|
|
13976
14059
|
};
|
|
13977
|
-
const existingPr = await findOpenPr(executor, conn,
|
|
13978
|
-
debug(config, `Existing open PR for ${
|
|
14060
|
+
const existingPr = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
|
|
14061
|
+
debug(config, `Existing open PR for ${VERSION_PR_BRANCH}: ${existingPr === null ? "(none)" : `#${String(existingPr)}`}`);
|
|
13979
14062
|
if (existingPr === null) try {
|
|
13980
14063
|
await createPr(executor, conn, {
|
|
13981
14064
|
title,
|
|
13982
|
-
head:
|
|
14065
|
+
head: VERSION_PR_BRANCH,
|
|
13983
14066
|
base: "main",
|
|
13984
14067
|
body
|
|
13985
14068
|
});
|
|
@@ -13990,7 +14073,7 @@ async function runVersionMode(executor, config) {
|
|
|
13990
14073
|
};
|
|
13991
14074
|
} catch (error) {
|
|
13992
14075
|
debug(config, `createPr failed, retrying as update: ${describeError(error)}`);
|
|
13993
|
-
const racePr = await findOpenPr(executor, conn,
|
|
14076
|
+
const racePr = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
|
|
13994
14077
|
if (racePr === null) throw error;
|
|
13995
14078
|
await updatePr(executor, conn, racePr, {
|
|
13996
14079
|
title,
|
|
@@ -14667,8 +14750,6 @@ z.object({
|
|
|
14667
14750
|
});
|
|
14668
14751
|
//#endregion
|
|
14669
14752
|
//#region src/release/publish/version-merge.ts
|
|
14670
|
-
/** The changesets version-PR branch this command merges. Not configurable. */
|
|
14671
|
-
const HEAD_BRANCH = "changeset-release/main";
|
|
14672
14753
|
/**
|
|
14673
14754
|
* Emptiness outranks mergeability: an empty version PR must be reported as content-free, not as a
|
|
14674
14755
|
* blocked merge -- that is the clobbered state this gate exists to catch. `unconfirmed` exists
|
|
@@ -14786,8 +14867,8 @@ function evaluateConfirmingRead(second, first, delayMs) {
|
|
|
14786
14867
|
};
|
|
14787
14868
|
}
|
|
14788
14869
|
async function mergeVersionPrForgejo(executor, conn, options) {
|
|
14789
|
-
const prNumber = await findOpenPr(executor, conn,
|
|
14790
|
-
if (prNumber === null) throw new FatalError(`No open PR found for branch ${
|
|
14870
|
+
const prNumber = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
|
|
14871
|
+
if (prNumber === null) throw new FatalError(`No open PR found for branch ${VERSION_PR_BRANCH}`);
|
|
14791
14872
|
const sleep$1 = options.sleep ?? sleep;
|
|
14792
14873
|
const recheckDelayMs = envMsOverride(process.env["BST_TEST_VERSION_MERGE_RECHECK_MS"], RECHECK_DELAY_MS);
|
|
14793
14874
|
const firstGate = await readMergeGate(executor, conn, prNumber);
|
|
@@ -14803,7 +14884,7 @@ async function mergeVersionPrForgejo(executor, conn, options) {
|
|
|
14803
14884
|
}
|
|
14804
14885
|
const headSha = headShaOrThrow(verdict);
|
|
14805
14886
|
if (options.dryRun) {
|
|
14806
|
-
log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${
|
|
14887
|
+
log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${VERSION_PR_BRANCH}`);
|
|
14807
14888
|
return;
|
|
14808
14889
|
}
|
|
14809
14890
|
await mergePr(executor, conn, prNumber, {
|
|
@@ -14811,7 +14892,7 @@ async function mergeVersionPrForgejo(executor, conn, options) {
|
|
|
14811
14892
|
deleteBranch: true,
|
|
14812
14893
|
expectedHeadSha: headSha
|
|
14813
14894
|
});
|
|
14814
|
-
log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${
|
|
14895
|
+
log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${VERSION_PR_BRANCH}`);
|
|
14815
14896
|
}
|
|
14816
14897
|
const GitHubVersionPrSchema = z.object({
|
|
14817
14898
|
number: z.number(),
|
|
@@ -14825,13 +14906,13 @@ function mergeVersionPrGitHub(executor, options) {
|
|
|
14825
14906
|
const view = executor.exec("gh", [
|
|
14826
14907
|
"pr",
|
|
14827
14908
|
"view",
|
|
14828
|
-
|
|
14909
|
+
VERSION_PR_BRANCH,
|
|
14829
14910
|
"--json",
|
|
14830
14911
|
"number,state,isDraft,mergeable,headRefOid,changedFiles"
|
|
14831
14912
|
], { cwd: process.cwd() });
|
|
14832
|
-
if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${
|
|
14913
|
+
if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${VERSION_PR_BRANCH}: ${view.stderr || view.stdout || "unknown error"}`);
|
|
14833
14914
|
const parsed = GitHubVersionPrSchema.safeParse(JSON.parse(view.stdout));
|
|
14834
|
-
if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${
|
|
14915
|
+
if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${VERSION_PR_BRANCH}: ${parsed.error.message}`);
|
|
14835
14916
|
const data = parsed.data;
|
|
14836
14917
|
const emptiness = data.changedFiles === 0 ? "empty" : "non-empty";
|
|
14837
14918
|
const mergeable = data.mergeable === "CONFLICTING" ? "conflicted" : "mergeable";
|
|
@@ -14845,20 +14926,20 @@ function mergeVersionPrGitHub(executor, options) {
|
|
|
14845
14926
|
emptiness
|
|
14846
14927
|
}));
|
|
14847
14928
|
if (options.dryRun) {
|
|
14848
|
-
log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${
|
|
14929
|
+
log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${VERSION_PR_BRANCH}`);
|
|
14849
14930
|
return;
|
|
14850
14931
|
}
|
|
14851
14932
|
const result = executor.exec("gh", [
|
|
14852
14933
|
"pr",
|
|
14853
14934
|
"merge",
|
|
14854
|
-
|
|
14935
|
+
VERSION_PR_BRANCH,
|
|
14855
14936
|
"--merge",
|
|
14856
14937
|
"--delete-branch",
|
|
14857
14938
|
"--match-head-commit",
|
|
14858
14939
|
headSha
|
|
14859
14940
|
], { cwd: process.cwd() });
|
|
14860
14941
|
if (result.exitCode !== 0) throw new FatalError(`Failed to merge PR: ${result.stderr || result.stdout || "unknown error"}`);
|
|
14861
|
-
log$1.info(`Merged changesets PR and deleted branch ${
|
|
14942
|
+
log$1.info(`Merged changesets PR and deleted branch ${VERSION_PR_BRANCH}`);
|
|
14862
14943
|
}
|
|
14863
14944
|
//#endregion
|
|
14864
14945
|
//#region src/commands/changesets-merge.ts
|
|
@@ -30445,7 +30526,7 @@ setForgejoLogger(log$1);
|
|
|
30445
30526
|
* `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
|
|
30446
30527
|
* can't pass itself off as the published release whose version it carries.
|
|
30447
30528
|
*/
|
|
30448
|
-
const displayVersion = "0.
|
|
30529
|
+
const displayVersion = "0.80.0";
|
|
30449
30530
|
/**
|
|
30450
30531
|
* Wait until a stream's internally-buffered data has been flushed to the OS.
|
|
30451
30532
|
*
|
package/package.json
CHANGED
package/tooling.schema.json
CHANGED
|
@@ -577,6 +577,10 @@
|
|
|
577
577
|
"items": {
|
|
578
578
|
"type": "string"
|
|
579
579
|
}
|
|
580
|
+
},
|
|
581
|
+
"runOnVersionPr": {
|
|
582
|
+
"description": "Run the CI check job on the changesets version PR (changeset-release/main). Default false: the generated check job is skipped there, because that branch is regenerated on every push to main and main's own run gates the release. Has no effect unless releaseStrategy is changesets.",
|
|
583
|
+
"type": "boolean"
|
|
580
584
|
}
|
|
581
585
|
},
|
|
582
586
|
"additionalProperties": false
|