* fix(#2988): local changeset/docs lint falls back to next, not main Both scripts/changeset/lint.cjs and scripts/lint-docs-required.cjs resolved their diff base as GITHUB_BASE_REF || 'main'. GITHUB_BASE_REF is set only in GitHub Actions; locally it falls back to 'main' (the release branch), which lags far behind 'next' (the integration branch every PR targets). The oversized diff range swept in every changeset fragment merged since the last release, so the lint passed on the first fragment it saw regardless of whether the current PR authored it — structurally vacuous. Changed the fallback to 'next' (DEFAULT_BASE constant, exported from both scripts for parity). CI behavior unchanged (GITHUB_BASE_REF is set there). Added a parity test asserting both lints resolve the same base. * chore(#2988): backfill changeset PR number 3095 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
5
.changeset/witty-yaks-munch.md
Normal file
5
.changeset/witty-yaks-munch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3095
|
||||
---
|
||||
**Local `lint:changeset` and `lint:docs-required` now diff against `next` instead of `main`** — the local fallback was the release branch (`main`), which lags far behind the integration branch (`next`), so the lint always passed by finding fragments from other already-merged PRs in the oversized diff range. The local invocation now matches the base CI uses. (#2988)
|
||||
@@ -12,6 +12,10 @@
|
||||
* Tests assert on the typed verdict, never on free text.
|
||||
*/
|
||||
|
||||
// #2988: the repo's integration/default branch — the base every PR targets.
|
||||
// Used as the local fallback when GITHUB_BASE_REF is unset (CI sets it).
|
||||
const DEFAULT_BASE = 'next';
|
||||
|
||||
const LINT_REASON = Object.freeze({
|
||||
OK_FRAGMENT_PRESENT: 'ok_fragment_present',
|
||||
OK_OPT_OUT_LABEL: 'ok_opt_out_label',
|
||||
@@ -80,7 +84,10 @@ function main() {
|
||||
labels = (event.pull_request?.labels || []).map((l) => l.name);
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
const base = process.env.GITHUB_BASE_REF || 'main';
|
||||
// #2988: local fallback must match the repo's integration branch (`next`),
|
||||
// not the release branch (`main`). CI sets GITHUB_BASE_REF explicitly; the
|
||||
// fallback only fires locally, where `next` is the base every PR targets.
|
||||
const base = process.env.GITHUB_BASE_REF || DEFAULT_BASE;
|
||||
let changedFiles = [];
|
||||
try {
|
||||
// Use execFileSync with an argv array — the base ref is interpolated
|
||||
@@ -145,4 +152,4 @@ function main() {
|
||||
|
||||
if (require.main === module) runMain(main);
|
||||
|
||||
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment };
|
||||
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment, DEFAULT_BASE };
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
const { parseFragment, FRAGMENT_ERROR } = require('./changeset/parse.cjs');
|
||||
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
||||
|
||||
// #2988: the repo's integration/default branch — the base every PR targets.
|
||||
// Used as the local fallback when GITHUB_BASE_REF is unset (CI sets it).
|
||||
const DEFAULT_BASE = 'next';
|
||||
|
||||
const LINT_REASON = Object.freeze({
|
||||
OK_NO_TRIGGERING_FRAGMENTS: 'ok_no_triggering_fragments',
|
||||
OK_DOCS_UPDATED: 'ok_docs_updated',
|
||||
@@ -149,7 +153,10 @@ function main() {
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
|
||||
const base = process.env.GITHUB_BASE_REF || 'main';
|
||||
// #2988: local fallback must match the repo's integration branch (`next`),
|
||||
// not the release branch (`main`). CI sets GITHUB_BASE_REF explicitly; the
|
||||
// fallback only fires locally, where `next` is the base every PR targets.
|
||||
const base = process.env.GITHUB_BASE_REF || DEFAULT_BASE;
|
||||
let changedFiles = [];
|
||||
try {
|
||||
// execFileSync with argv — no shell, so a malicious GITHUB_BASE_REF
|
||||
@@ -216,6 +223,7 @@ module.exports = {
|
||||
OPT_OUT_LABEL,
|
||||
TRIGGERING_TYPES,
|
||||
FRAGMENT_ERROR,
|
||||
DEFAULT_BASE,
|
||||
isFragmentPath,
|
||||
isDocsFile,
|
||||
isExemptFragment,
|
||||
|
||||
@@ -8,7 +8,8 @@ const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const cp = require('node:child_process');
|
||||
|
||||
const { evaluateLint, LINT_REASON } = require(path.join(__dirname, '..', 'scripts', 'changeset', 'lint.cjs'));
|
||||
const { evaluateLint, LINT_REASON, DEFAULT_BASE: CHANGESET_DEFAULT_BASE } = require(path.join(__dirname, '..', 'scripts', 'changeset', 'lint.cjs'));
|
||||
const { DEFAULT_BASE: DOCS_DEFAULT_BASE } = require(path.join(__dirname, '..', 'scripts', 'lint-docs-required.cjs'));
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const LINT_SCRIPT = path.join(ROOT, 'scripts', 'changeset', 'lint.cjs');
|
||||
@@ -297,3 +298,19 @@ describe('changeset lint: main() end-to-end wiring (#1006)', () => {
|
||||
assert.ok(!deletedEntry, `deleted fragment must not appear in failures, got: ${JSON.stringify(failures)}`);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── #2988: local base fallback parity ──────────────────────────────────────
|
||||
|
||||
describe('#2988: changeset + docs lints resolve the same local base fallback', () => {
|
||||
test('both lints default to `next` (the integration branch), not `main`', () => {
|
||||
assert.strictEqual(CHANGESET_DEFAULT_BASE, 'next',
|
||||
`changeset lint DEFAULT_BASE must be 'next', got '${CHANGESET_DEFAULT_BASE}'`);
|
||||
assert.strictEqual(DOCS_DEFAULT_BASE, 'next',
|
||||
`docs lint DEFAULT_BASE must be 'next', got '${DOCS_DEFAULT_BASE}'`);
|
||||
});
|
||||
|
||||
test('both lints resolve the same base given the same environment (parity)', () => {
|
||||
assert.strictEqual(CHANGESET_DEFAULT_BASE, DOCS_DEFAULT_BASE,
|
||||
`the two lints must not diverge on base resolution: changeset='${CHANGESET_DEFAULT_BASE}' docs='${DOCS_DEFAULT_BASE}'`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user