From 42f1b5e2ca7832217daa005edd1bb52b67b155ee Mon Sep 17 00:00:00 2001 From: JulianMaurin Date: Tue, 30 Jun 2026 11:01:28 +0200 Subject: [PATCH] docs(conditions): split GitHub-sourced attributes into their own section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The attributes list now has two subsections under "Attributes List": Mergify attributes and GitHub rulesets and branch protection attributes. The latter holds the attributes carrying x-mergify-attribute-metadata.source = "github" — read-only values Mergify loads from GitHub that cannot be set in the configuration. A getAttributeSource() accessor reads the schema metadata; both render paths (the PullRequestAttributes React table and the schemaToMarkdown generator) partition on it through an optional source filter. Co-Authored-By: Claude Opus 4.8 (1M context) Change-Id: I200fc09a8c36b3c3ec357b69ef44157257abe3c8 --- .../Tables/PullRequestAttributes.tsx | 21 +++++++++++++--- src/content/docs/configuration/conditions.mdx | 15 ++++++++++++ src/util/attributeMetadata.test.ts | 24 +++++++++++++++++++ src/util/attributeMetadata.ts | 23 ++++++++++++++++++ src/util/schemaToMarkdown.test.ts | 14 +++++++++++ src/util/schemaToMarkdown.ts | 17 +++++++++---- 6 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 src/util/attributeMetadata.test.ts create mode 100644 src/util/attributeMetadata.ts diff --git a/src/components/Tables/PullRequestAttributes.tsx b/src/components/Tables/PullRequestAttributes.tsx index 20605d1580..a9623e116e 100644 --- a/src/components/Tables/PullRequestAttributes.tsx +++ b/src/components/Tables/PullRequestAttributes.tsx @@ -1,8 +1,11 @@ +import { getAttributeSource } from '../../util/attributeMetadata'; import configSchema from '../../util/sanitizedConfigSchema'; import { getValueType } from './ConfigOptions'; import { renderMarkdown } from './utils'; +type Attributes = typeof configSchema.$defs.PullRequestAttributes.properties; + // The engine annotates each condition attribute with its authoritative operators // and modifiers (`x-allowed-operators` / `x-modifiers`). The generated TypeScript // types drop `x-` keys, so they are read from the imported JSON via this shape. @@ -15,7 +18,10 @@ interface ConditionMeta { } interface Props { - staticAttributes: typeof configSchema.$defs.PullRequestAttributes.properties; + staticAttributes?: Attributes; + // When set, render only attributes whose metadata source matches (e.g. "github"). + // When unset, render only attributes that have no source (config-writable ones). + source?: string; } function renderOperators(operators: string[]) { @@ -60,8 +66,17 @@ function renderModifiers(modifiers: ConditionMeta['x-modifiers']) { return parts.flatMap((part, index) => (index === 0 ? [part] : [' ', part])); } -export default function PullRequestAttributes({ staticAttributes }: Props) { - const attributes = staticAttributes ?? configSchema.$defs.PullRequestAttributes.properties; +export default function PullRequestAttributes({ staticAttributes, source }: Props) { + // The search-highlight path passes a pre-filtered subset; render it as-is. + // Otherwise partition the canonical list by metadata source. + const attributes = + staticAttributes ?? + (Object.fromEntries( + Object.entries(configSchema.$defs.PullRequestAttributes.properties).filter(([, value]) => { + const attributeSource = getAttributeSource(value); + return source ? attributeSource === source : attributeSource === undefined; + }) + ) as Attributes); const entries = Object.entries(attributes).sort(([keyA], [keyB]) => (keyA > keyB ? 1 : -1)); diff --git a/src/content/docs/configuration/conditions.mdx b/src/content/docs/configuration/conditions.mdx index 529153954a..e8b6f09fb2 100644 --- a/src/content/docs/configuration/conditions.mdx +++ b/src/content/docs/configuration/conditions.mdx @@ -67,8 +67,23 @@ Boolean attributes are used on their own or negated with `-`: ## Attributes List +Conditions are evaluated against the following pull request attributes, split +into Mergify attributes and a read-only set loaded from GitHub. + +### Mergify Attributes + +### GitHub Rulesets and Branch Protection Attributes + +Mergify loads these attributes from the GitHub rulesets and branch protection +rules active on the pull request's base branch. They are read-only: Mergify +populates their values from GitHub, so you cannot set them in your +configuration. Use them in conditions to gate on the same review and protection +requirements that GitHub enforces. + + + ## Operators List diff --git a/src/util/attributeMetadata.test.ts b/src/util/attributeMetadata.test.ts new file mode 100644 index 0000000000..a1a11c0a04 --- /dev/null +++ b/src/util/attributeMetadata.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from 'vitest'; +import { getAttributeSource } from './attributeMetadata'; + +describe('getAttributeSource', () => { + test('returns the source when present', () => { + expect(getAttributeSource({ 'x-mergify-attribute-metadata': { source: 'github' } })).toBe( + 'github' + ); + }); + + test('returns undefined when the attribute has no metadata', () => { + expect(getAttributeSource({ type: 'boolean' })).toBeUndefined(); + }); + + test('returns undefined for a missing or malformed source', () => { + expect(getAttributeSource({ 'x-mergify-attribute-metadata': {} })).toBeUndefined(); + expect(getAttributeSource({ 'x-mergify-attribute-metadata': { source: 42 } })).toBeUndefined(); + }); + + test('returns undefined for non-object input', () => { + expect(getAttributeSource(null)).toBeUndefined(); + expect(getAttributeSource('github')).toBeUndefined(); + }); +}); diff --git a/src/util/attributeMetadata.ts b/src/util/attributeMetadata.ts new file mode 100644 index 0000000000..8acfa955fa --- /dev/null +++ b/src/util/attributeMetadata.ts @@ -0,0 +1,23 @@ +// Pull-request attribute definitions publish provenance under this custom +// JSON-schema key. +const ATTRIBUTE_METADATA_KEY = 'x-mergify-attribute-metadata'; + +/** + * Return the `source` recorded on a pull-request attribute definition, or + * `undefined` when the attribute carries no metadata. A `github` source marks + * attributes that Mergify loads from GitHub rulesets and branch protection: + * they are read-only and cannot be set in the configuration. + */ +export function getAttributeSource(definition: unknown): string | undefined { + if (!definition || typeof definition !== 'object') { + return undefined; + } + + const metadata = (definition as Record)[ATTRIBUTE_METADATA_KEY]; + if (metadata && typeof metadata === 'object') { + const source = (metadata as Record).source; + return typeof source === 'string' ? source : undefined; + } + + return undefined; +} diff --git a/src/util/schemaToMarkdown.test.ts b/src/util/schemaToMarkdown.test.ts index 78fcf7f659..974793a9b7 100644 --- a/src/util/schemaToMarkdown.test.ts +++ b/src/util/schemaToMarkdown.test.ts @@ -30,6 +30,20 @@ describe('expandMdxComponents', () => { expect(result).not.toContain('PullRequestAttributesTable'); }); + test('default PullRequestAttributesTable excludes GitHub-sourced attributes', () => { + const result = expandMdxComponents(''); + expect(result).toContain('`author`'); + expect(result).not.toContain('`github-review-approved`'); + }); + + test('PullRequestAttributesTable source="github" lists only GitHub-sourced attributes', () => { + const result = expandMdxComponents(''); + expect(result).toContain('`github-review-approved`'); + expect(result).toContain('`github-code-owner-review-satisfied`'); + expect(result).not.toContain('`author`'); + expect(result).not.toContain('PullRequestAttributesTable'); + }); + test('removes import statements', () => { const input = [ 'import OptionsTable from "../../../components/Tables/OptionsTable";', diff --git a/src/util/schemaToMarkdown.ts b/src/util/schemaToMarkdown.ts index 094df78fee..72de1b786f 100644 --- a/src/util/schemaToMarkdown.ts +++ b/src/util/schemaToMarkdown.ts @@ -1,5 +1,6 @@ import jsonpointer from 'jsonpointer'; +import { getAttributeSource } from './attributeMetadata'; import configSchema from './sanitizedConfigSchema'; type Schema = typeof configSchema; @@ -99,9 +100,14 @@ function generateOptionsTable(defName: string): string { return [headerLine, separatorLine, ...rowLines].join('\n'); } -function generatePullRequestAttributesTable(): string { +function generatePullRequestAttributesTable(source?: string): string { const attributes = (configSchema as any).$defs.PullRequestAttributes.properties; - const entries = Object.entries(attributes).sort(([a], [b]) => a.localeCompare(b)); + const entries = Object.entries(attributes) + .filter(([, value]) => { + const attributeSource = getAttributeSource(value); + return source ? attributeSource === source : attributeSource === undefined; + }) + .sort(([a], [b]) => a.localeCompare(b)); const rows: string[][] = []; for (const [key, value] of entries) { @@ -178,9 +184,10 @@ export function expandMdxComponents(source: string): string { generateOptionsTable(def) ); - // Replace - source = source.replace(//g, () => - generatePullRequestAttributesTable() + // Replace (optionally filtered by source) + source = source.replace( + //g, + (_match, attributeSource) => generatePullRequestAttributesTable(attributeSource) ); // Collapse runs of 3+ blank lines to 2