Skip to content

Commit 0045ae3

Browse files
authored
Make GraphQL changelog script future-compatible with new change types (#56276)
1 parent 6e22310 commit 0045ae3

File tree

4 files changed

+239
-44
lines changed

4 files changed

+239
-44
lines changed

.github/workflows/sync-graphql.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ jobs:
1717
update_graphql_files:
1818
if: github.repository == 'github/docs-internal'
1919
runs-on: ubuntu-latest
20+
outputs:
21+
ignored-changes: ${{ steps.sync.outputs.ignored-changes }}
22+
ignored-count: ${{ steps.sync.outputs.ignored-count }}
23+
ignored-types: ${{ steps.sync.outputs.ignored-types }}
2024
steps:
2125
- name: Checkout
2226
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2327
- uses: ./.github/actions/node-npm-setup
2428
- name: Run updater scripts
29+
id: sync
2530
env:
2631
# need to use a token from a user with access to github/github for this step
2732
GITHUB_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }}
@@ -70,3 +75,22 @@ jobs:
7075
with:
7176
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
7277
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
78+
79+
notify_ignored_changes:
80+
if: github.repository == 'github/docs-internal' && needs.update_graphql_files.outputs.ignored-count > 0 && github.event_name != 'workflow_dispatch'
81+
needs: update_graphql_files
82+
runs-on: ubuntu-latest
83+
steps:
84+
- name: Checkout
85+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
86+
- uses: ./.github/actions/slack-alert
87+
with:
88+
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
89+
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
90+
color: warning
91+
message: |
92+
⚠️ GraphQL Sync found ${{ needs.update_graphql_files.outputs.ignored-count }} ignored change types: ${{ needs.update_graphql_files.outputs.ignored-types }}
93+
94+
These change types are not in CHANGES_TO_REPORT and were silently ignored. Consider reviewing if they should be added to the changelog.
95+
96+
See workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

src/graphql/scripts/build-changelog.js

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,34 @@ export async function createChangelogEntry(
5050
// Generate changes between the two schemas
5151
const changes = await diff(oldSchema, newSchema)
5252
const changesToReport = []
53+
const ignoredChanges = []
5354
changes.forEach((change) => {
5455
if (CHANGES_TO_REPORT.includes(change.type)) {
5556
changesToReport.push(change)
56-
} else if (CHANGES_TO_IGNORE.includes(change.type)) {
57-
// Do nothing
5857
} else {
59-
console.error('Change object causing error:')
60-
console.error(change)
61-
throw new Error(
62-
'This change type should be added to CHANGES_TO_REPORT or CHANGES_TO_IGNORE: ' +
63-
change.type,
64-
)
58+
// Track ignored changes for visibility
59+
ignoredChanges.push(change)
6560
}
6661
})
6762

63+
// Log warnings for ignored change types to provide visibility
64+
if (ignoredChanges.length > 0) {
65+
const ignoredTypes = [...new Set(ignoredChanges.map((change) => change.type))]
66+
console.warn(
67+
`⚠️ GraphQL changelog: Ignoring ${ignoredChanges.length} changes of ${ignoredTypes.length} type(s):`,
68+
)
69+
ignoredTypes.forEach((type) => {
70+
const count = ignoredChanges.filter((change) => change.type === type).length
71+
console.warn(` - ${type} (${count} change${count > 1 ? 's' : ''})`)
72+
})
73+
console.warn(
74+
' These change types are not in CHANGES_TO_REPORT and will not appear in the changelog.',
75+
)
76+
}
77+
78+
// Store ignored changes for potential workflow outputs
79+
createChangelogEntry.lastIgnoredChanges = ignoredChanges
80+
6881
const { schemaChangesToReport, previewChangesToReport } = segmentPreviewChanges(
6982
changesToReport,
7083
previews,
@@ -276,40 +289,36 @@ const CHANGES_TO_REPORT = [
276289
ChangeType.DirectiveUsageFieldDefinitionRemoved,
277290
]
278291

279-
const CHANGES_TO_IGNORE = [
280-
ChangeType.FieldArgumentDescriptionChanged,
281-
ChangeType.DirectiveRemoved,
282-
ChangeType.DirectiveAdded,
283-
ChangeType.DirectiveDescriptionChanged,
284-
ChangeType.DirectiveLocationAdded,
285-
ChangeType.DirectiveLocationRemoved,
286-
ChangeType.DirectiveArgumentAdded,
287-
ChangeType.DirectiveArgumentRemoved,
288-
ChangeType.DirectiveArgumentDescriptionChanged,
289-
ChangeType.DirectiveArgumentDefaultValueChanged,
290-
ChangeType.DirectiveArgumentTypeChanged,
291-
ChangeType.DirectiveUsageArgumentDefinitionRemoved,
292-
ChangeType.EnumValueDescriptionChanged,
293-
ChangeType.EnumValueDeprecationReasonChanged,
294-
ChangeType.EnumValueDeprecationReasonAdded,
295-
ChangeType.EnumValueDeprecationReasonRemoved,
296-
ChangeType.FieldDescriptionChanged,
297-
ChangeType.FieldDescriptionAdded,
298-
ChangeType.FieldDescriptionRemoved,
299-
ChangeType.FieldDeprecationAdded,
300-
ChangeType.FieldDeprecationRemoved,
301-
ChangeType.FieldDeprecationReasonChanged,
302-
ChangeType.FieldDeprecationReasonAdded,
303-
ChangeType.FieldDeprecationReasonRemoved,
304-
ChangeType.InputFieldDescriptionAdded,
305-
ChangeType.InputFieldDescriptionRemoved,
306-
ChangeType.InputFieldDescriptionChanged,
307-
ChangeType.TypeDescriptionChanged,
308-
ChangeType.TypeDescriptionRemoved,
309-
ChangeType.TypeDescriptionAdded,
310-
ChangeType.DirectiveUsageFieldDefinitionAdded,
311-
ChangeType.DirectiveUsageArgumentDefinitionAdded,
312-
ChangeType.DirectiveUsageEnumValueAdded,
313-
]
292+
// CHANGES_TO_IGNORE list removed - now we only process changes explicitly listed
293+
// in CHANGES_TO_REPORT and silently ignore all others for future compatibility
294+
295+
/**
296+
* Get the ignored change types from the last changelog entry creation
297+
* @returns {Array} Array of ignored change objects
298+
*/
299+
export function getLastIgnoredChanges() {
300+
return createChangelogEntry.lastIgnoredChanges || []
301+
}
302+
303+
/**
304+
* Get summary of ignored change types for workflow outputs
305+
* @returns {Object} Summary with counts and types
306+
*/
307+
export function getIgnoredChangesSummary() {
308+
const ignored = getLastIgnoredChanges()
309+
if (ignored.length === 0) return null
310+
311+
const types = [...new Set(ignored.map((change) => change.type))]
312+
const summary = {
313+
totalCount: ignored.length,
314+
typeCount: types.length,
315+
types: types.map((type) => ({
316+
type,
317+
count: ignored.filter((change) => change.type === type).length,
318+
})),
319+
}
320+
321+
return summary
322+
}
314323

315324
export default { createChangelogEntry, cleanPreviewTitle, previewAnchor, prependDatedEntry }

src/graphql/scripts/sync.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs/promises'
2+
import { appendFileSync } from 'fs'
23
import path from 'path'
34
import { mkdirp } from 'mkdirp'
45
import yaml from 'js-yaml'
@@ -8,7 +9,11 @@ import { allVersions } from '@/versions/lib/all-versions'
89
import processPreviews from './utils/process-previews'
910
import processUpcomingChanges from './utils/process-upcoming-changes'
1011
import processSchemas from './utils/process-schemas'
11-
import { prependDatedEntry, createChangelogEntry } from './build-changelog'
12+
import {
13+
prependDatedEntry,
14+
createChangelogEntry,
15+
getIgnoredChangesSummary,
16+
} from './build-changelog'
1217

1318
const graphqlStaticDir = 'src/graphql/data'
1419
const dataFilenames = JSON.parse(await fs.readFile('src/graphql/scripts/utils/data-filenames.json'))
@@ -22,6 +27,8 @@ const versionsToBuild = Object.keys(allVersions)
2227

2328
main()
2429

30+
let allIgnoredChanges = []
31+
2532
async function main() {
2633
for (const version of versionsToBuild) {
2734
// Get the relevant GraphQL name for the current version
@@ -77,11 +84,42 @@ async function main() {
7784
path.join(graphqlStaticDir, graphqlVersion, 'changelog.json'),
7885
)
7986
}
87+
88+
// Capture ignored changes for potential workflow notifications
89+
const ignoredSummary = getIgnoredChangesSummary()
90+
if (ignoredSummary) {
91+
allIgnoredChanges.push({
92+
version: graphqlVersion,
93+
...ignoredSummary,
94+
})
95+
}
8096
}
8197
}
8298

8399
// Ensure the YAML linter runs before checkinging in files
84100
execSync('npx prettier -w "**/*.{yml,yaml}"')
101+
102+
// Output ignored changes for GitHub Actions
103+
if (allIgnoredChanges.length > 0) {
104+
const totalIgnored = allIgnoredChanges.reduce((sum, item) => sum + item.totalCount, 0)
105+
const uniqueTypes = [
106+
...new Set(allIgnoredChanges.flatMap((item) => item.types.map((t) => t.type))),
107+
]
108+
109+
console.log(
110+
'::notice title=GraphQL Ignored Changes::Found ignored change types that may need review',
111+
)
112+
113+
// Write outputs to GitHub Actions output file
114+
if (process.env.GITHUB_OUTPUT) {
115+
appendFileSync(
116+
process.env.GITHUB_OUTPUT,
117+
`ignored-changes=${JSON.stringify(allIgnoredChanges)}\n`,
118+
)
119+
appendFileSync(process.env.GITHUB_OUTPUT, `ignored-count=${totalIgnored}\n`)
120+
appendFileSync(process.env.GITHUB_OUTPUT, `ignored-types=${uniqueTypes.join(', ')}\n`)
121+
}
122+
}
85123
}
86124

87125
// get latest from github/github

src/graphql/tests/build-changelog.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
cleanPreviewTitle,
1010
previewAnchor,
1111
prependDatedEntry,
12+
getLastIgnoredChanges,
13+
getIgnoredChangesSummary,
1214
} from '../scripts/build-changelog'
1315
import readJsonFile from '@/frame/lib/read-json-file'
1416

@@ -22,6 +24,60 @@ describe('creating a changelog from old schema and new schema', () => {
2224
MockDate.reset()
2325
})
2426

27+
test('ignores unknown change types without throwing errors', async () => {
28+
// Create a minimal test that would generate an unknown change type
29+
// This test ensures the system gracefully handles new change types
30+
const oldSchemaString = `
31+
type Query {
32+
field: String
33+
}
34+
`
35+
36+
const newSchemaString = `
37+
"""
38+
Updated description for Query type
39+
"""
40+
type Query {
41+
field: String
42+
}
43+
`
44+
45+
// This should generate TypeDescriptionAdded change type
46+
// which should be silently ignored if not in CHANGES_TO_REPORT
47+
const entry = await createChangelogEntry(oldSchemaString, newSchemaString, [], [], [])
48+
49+
// Should return null since TypeDescriptionAdded is not in CHANGES_TO_REPORT
50+
// and will be silently ignored without throwing an error
51+
expect(entry).toBeNull()
52+
})
53+
54+
test('handles new directive usage change types gracefully', async () => {
55+
// Test that verifies the system can handle new directive-related change types
56+
// that were previously causing errors in the pipeline
57+
const oldSchemaString = `
58+
directive @example on FIELD_DEFINITION
59+
60+
type Query {
61+
field: String
62+
}
63+
`
64+
65+
const newSchemaString = `
66+
directive @example on FIELD_DEFINITION
67+
68+
type Query {
69+
field: String @example
70+
}
71+
`
72+
73+
// This should generate DirectiveUsage* change types that are not in CHANGES_TO_REPORT
74+
// The system should silently ignore these and not throw errors
75+
const entry = await createChangelogEntry(oldSchemaString, newSchemaString, [], [], [])
76+
77+
// Should return null since directive usage changes are typically ignored
78+
expect(entry).toBeNull()
79+
})
80+
2581
test('finds a diff of schema changes, upcoming changes, and preview changes', async () => {
2682
const oldSchemaString = `
2783
type PreviewType {
@@ -146,3 +202,71 @@ describe('updating the changelog file', () => {
146202
expect(JSON.parse(newContents)).toEqual(expectedUpdatedChangelogFile)
147203
})
148204
})
205+
206+
describe('ignored changes tracking', () => {
207+
test('tracks ignored change types', async () => {
208+
const oldSchemaString = `
209+
type Query {
210+
field: String
211+
}
212+
`
213+
214+
const newSchemaString = `
215+
"""
216+
Updated description for Query type
217+
"""
218+
type Query {
219+
field: String
220+
}
221+
`
222+
223+
// This should generate a TypeDescriptionAdded change type that gets ignored
224+
await createChangelogEntry(oldSchemaString, newSchemaString, [], [], [])
225+
226+
const ignoredChanges = getLastIgnoredChanges()
227+
expect(ignoredChanges.length).toBe(1)
228+
expect(ignoredChanges[0].type).toBe('TYPE_DESCRIPTION_ADDED')
229+
})
230+
231+
test('provides ignored changes summary', async () => {
232+
const oldSchemaString = `
233+
directive @example on FIELD_DEFINITION
234+
type Query {
235+
field1: String
236+
field2: Int
237+
}
238+
`
239+
240+
const newSchemaString = `
241+
directive @example on FIELD_DEFINITION
242+
type Query {
243+
field1: String @example
244+
field2: Int @example
245+
}
246+
`
247+
248+
// This should generate multiple DirectiveUsage changes that get ignored
249+
await createChangelogEntry(oldSchemaString, newSchemaString, [], [], [])
250+
251+
const summary = getIgnoredChangesSummary()
252+
expect(summary).toBeTruthy()
253+
expect(summary.totalCount).toBe(2)
254+
expect(summary.typeCount).toBe(1)
255+
expect(summary.types[0].type).toBe('DIRECTIVE_USAGE_FIELD_DEFINITION_ADDED')
256+
expect(summary.types[0].count).toBe(2)
257+
})
258+
259+
test('returns null summary when no changes ignored', async () => {
260+
const schemaString = `
261+
type Query {
262+
field: String
263+
}
264+
`
265+
266+
// No changes should be generated
267+
await createChangelogEntry(schemaString, schemaString, [], [], [])
268+
269+
const summary = getIgnoredChangesSummary()
270+
expect(summary).toBeNull()
271+
})
272+
})

0 commit comments

Comments
 (0)