Support schemas with only extended types (#7630)

This commit is contained in:
jdolle 2026-02-05 18:02:44 -08:00 committed by GitHub
parent a65b0bc772
commit ef246a17fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 8 deletions

View file

@ -176,7 +176,7 @@ export function compareDirectiveLists(
const newItems = newMap[itemName];
const oldItems = oldMap[itemName];
for (let i = (oldItems ?? []).length; i < (newItems ?? []).length; i++) {
const oldItem = oldItems[i];
const oldItem = oldItems?.[i];
const newItem = newItems[i];
if (oldItem === undefined) {
added.push(newItem);

View file

@ -108,14 +108,14 @@ export function ChangeRow(props: {
'bg-neutral-2 px-2',
props.className,
props.type === 'removal' && 'bg-[#561c1d]',
props.type === 'addition' && 'bg-[#11362b]',
props.type === 'addition' && 'bg-green-600 dark:bg-[#11362b]',
)}
>
<span
className={cn(
'bg-neutral-2',
props.type === 'removal' && 'bg-[#561c1d] line-through decoration-[#998c8b]',
props.type === 'addition' && 'bg-[#11362b]',
props.type === 'addition' && 'bg-green-600 dark:bg-[#11362b]',
)}
>
{!!props.indent &&
@ -178,7 +178,14 @@ function Addition(props: { children: ReactNode; className?: string }): ReactNode
}
}, [change.addition]);
return (
<span className={cn('bg-neutral-3 hover:bg-green-900', props.className)}>{props.children}</span>
<span
className={cn(
'bg-green-600 hover:bg-green-700 dark:bg-[#11362b] dark:hover:bg-green-900',
props.className,
)}
>
{props.children}
</span>
);
}

View file

@ -1,5 +1,16 @@
import { useMemo } from 'react';
import { buildSchema, GraphQLSchema } from 'graphql';
import {
buildASTSchema,
buildSchema,
DefinitionNode,
DocumentNode,
GraphQLSchema,
isTypeDefinitionNode,
isTypeExtensionNode,
Kind,
parse,
visit,
} from 'graphql';
import { useMutation, useQuery } from 'urql';
import { Page, TargetLayout } from '@/components/layouts/target';
import { CompositionErrorsSection_SchemaErrorConnection } from '@/components/target/history/errors-and-changes';
@ -179,6 +190,68 @@ export function TargetProposalsSinglePage(props: {
);
}
const extensionToDefinitionKindMap = {
[Kind.OBJECT_TYPE_EXTENSION]: Kind.OBJECT_TYPE_DEFINITION,
[Kind.INPUT_OBJECT_TYPE_EXTENSION]: Kind.INPUT_OBJECT_TYPE_DEFINITION,
[Kind.INTERFACE_TYPE_EXTENSION]: Kind.INTERFACE_TYPE_DEFINITION,
[Kind.UNION_TYPE_EXTENSION]: Kind.UNION_TYPE_DEFINITION,
[Kind.ENUM_TYPE_EXTENSION]: Kind.ENUM_TYPE_DEFINITION,
[Kind.SCALAR_TYPE_EXTENSION]: Kind.SCALAR_TYPE_DEFINITION,
} as const;
function addTypeForExtensions(ast: DocumentNode) {
const trackTypeDefs = new Map<
string,
| {
state: 'TYPE_ONLY';
}
| {
state: 'EXTENSION_ONLY' | 'VALID_EXTENSION';
kind:
| Kind.OBJECT_TYPE_EXTENSION
| Kind.ENUM_TYPE_EXTENSION
| Kind.UNION_TYPE_EXTENSION
| Kind.SCALAR_TYPE_EXTENSION
| Kind.INTERFACE_TYPE_EXTENSION
| Kind.INPUT_OBJECT_TYPE_EXTENSION;
}
>();
for (const node of ast.definitions) {
if ('name' in node && node.name) {
const name = node.name.value;
const entry = trackTypeDefs.get(name);
if (isTypeExtensionNode(node)) {
if (!entry) {
trackTypeDefs.set(name, { state: 'EXTENSION_ONLY', kind: node.kind });
} else if (entry.state === 'TYPE_ONLY') {
trackTypeDefs.set(name, { kind: node.kind, state: 'VALID_EXTENSION' });
}
} else if (isTypeDefinitionNode(node)) {
if (!entry) {
trackTypeDefs.set(name, { state: 'TYPE_ONLY' });
} else if (entry.state === 'EXTENSION_ONLY') {
trackTypeDefs.set(name, { ...entry, state: 'VALID_EXTENSION' });
}
}
}
}
const astCopy = visit(ast, {});
for (const [name, entry] of trackTypeDefs) {
if (entry.state === 'EXTENSION_ONLY') {
console.log('FOUND EXTENSION: ', name, entry.kind);
(astCopy.definitions as DefinitionNode[]).push({
kind: extensionToDefinitionKindMap[entry.kind],
name: {
kind: Kind.NAME,
value: name,
},
});
}
}
return astCopy;
}
const ProposalsContent = (props: Parameters<typeof TargetProposalsSinglePage>[0]) => {
// fetch main page details
const [query, refreshProposal] = useQuery({
@ -252,9 +325,12 @@ const ProposalsContent = (props: Parameters<typeof TargetProposalsSinglePage>[0]
(proposalVersion.serviceName == null || proposalVersion.serviceName === '') */,
)?.node.source;
const beforeSchema = existingSchema?.length
? buildSchema(existingSchema, { assumeValid: true, assumeValidSDL: true })
: null;
let beforeSchema: GraphQLSchema | null = null;
if (existingSchema?.length) {
const ast = addTypeForExtensions(parse(existingSchema));
beforeSchema = buildASTSchema(ast, { assumeValid: true, assumeValidSDL: true });
}
// @todo better handle pagination
const allChanges =
proposalVersion.schemaChanges?.edges