From 1f7566f0bead301b21d4f7da49a4bc6924d07ef3 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 30 Sep 2026 20:06:19 +1000 Subject: [PATCH 1/3] [typescript-operations] fix: satisfy strict mode in typescript-operations (#4683) (#11009) Fixes the 10 errors `plugins/typescript/operations` hits once the root tsconfig enables `strict: true`, mostly by widening the shared types it calls into instead of adding defaults at each call site: - plugin-helpers: `ComplexPluginOutput.prepend`/`append` accept `null` items, since operations prepends `getExactUtilityType()` and `getIncrementalUtilityType()` (`string | null`). Core already skips falsy items, and `mergeOutputs` output stays the same (TS2322). - visitor-plugin-common: `DeclarationBlock.withComment` accepts `undefined` for `description?.value` callers (TS2345), `parseEnumValues`'s `mapOrStr` is optional to match its `{}` default (TS2322), `ImportSource.namespace` accepts `null` like `namespacedImportName` (TS2322), and `optimizeOperations`'s `includeFragments` is optional to match relay-operation-optimizer (TS2322). - operations: default the optional plugin `info` argument (TS2339), skip document files without a `document` (TS2345), and guard `d.name` (TS18048). Generated output is unchanged. The strict typecheck goes from 307 to 283 errors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VB2AkFW99jZT4EYQjhRzF4 eddeee888:oss:issue-fix Co-authored-by: Claude --- .changeset/strict-typescript-operations.md | 13 +++++++++++++ .../other/visitor-plugin-common/src/enum-values.ts | 2 +- .../other/visitor-plugin-common/src/imports.ts | 2 +- .../src/optimize-operations.ts | 2 +- .../other/visitor-plugin-common/src/utils.ts | 9 ++++++--- packages/plugins/typescript/operations/src/index.ts | 8 ++++++-- packages/utils/plugins-helpers/src/types.ts | 4 ++-- 7 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 .changeset/strict-typescript-operations.md diff --git a/.changeset/strict-typescript-operations.md b/.changeset/strict-typescript-operations.md new file mode 100644 index 00000000000..1b78b833b67 --- /dev/null +++ b/.changeset/strict-typescript-operations.md @@ -0,0 +1,13 @@ +--- +'@graphql-codegen/typescript-operations': patch +'@graphql-codegen/visitor-plugin-common': patch +'@graphql-codegen/plugin-helpers': patch +--- + +Fix type errors in `typescript-operations` under `strict: true` by widening the shared types it calls into: + +- `@graphql-codegen/plugin-helpers`: `Types.ComplexPluginOutput`'s `prepend` and `append` now accept `null` items. Core already skipped them. +- `@graphql-codegen/visitor-plugin-common`: `DeclarationBlock.withComment` accepts `undefined`, `parseEnumValues`'s `mapOrStr` is optional (it already defaulted to `{}`), `ImportSource.namespace` accepts `null`, and `optimizeOperations`'s `includeFragments` is optional. +- `@graphql-codegen/typescript-operations`: skips document files without a `document`, and no longer throws when called without the plugin info argument. + +Generated output is unchanged. diff --git a/packages/plugins/other/visitor-plugin-common/src/enum-values.ts b/packages/plugins/other/visitor-plugin-common/src/enum-values.ts index 6d87a956789..9d1bcb91329 100644 --- a/packages/plugins/other/visitor-plugin-common/src/enum-values.ts +++ b/packages/plugins/other/visitor-plugin-common/src/enum-values.ts @@ -14,7 +14,7 @@ export function parseEnumValues({ naming, }: { schema: GraphQLSchema; - mapOrStr: EnumValuesMap; + mapOrStr?: EnumValuesMap; ignoreEnumValuesFromSchema?: boolean; naming: { convert: ConvertFn; diff --git a/packages/plugins/other/visitor-plugin-common/src/imports.ts b/packages/plugins/other/visitor-plugin-common/src/imports.ts index 2d506d6f423..260c4c35364 100644 --- a/packages/plugins/other/visitor-plugin-common/src/imports.ts +++ b/packages/plugins/other/visitor-plugin-common/src/imports.ts @@ -21,7 +21,7 @@ export type ImportSource = { /** * Namespace to import source as */ - namespace?: string; + namespace?: string | null; /** * Entity names to import */ diff --git a/packages/plugins/other/visitor-plugin-common/src/optimize-operations.ts b/packages/plugins/other/visitor-plugin-common/src/optimize-operations.ts index e4c6f8b0066..1d357490e78 100644 --- a/packages/plugins/other/visitor-plugin-common/src/optimize-operations.ts +++ b/packages/plugins/other/visitor-plugin-common/src/optimize-operations.ts @@ -5,7 +5,7 @@ import { optimizeDocuments } from '@graphql-tools/relay-operation-optimizer'; export function optimizeOperations( schema: GraphQLSchema, documents: Types.DocumentFile[], - options?: { includeFragments: boolean }, + options?: { includeFragments?: boolean }, ): Types.DocumentFile[] { const newDocuments = optimizeDocuments( schema, diff --git a/packages/plugins/other/visitor-plugin-common/src/utils.ts b/packages/plugins/other/visitor-plugin-common/src/utils.ts index 1714d32c6e2..32137bc6604 100644 --- a/packages/plugins/other/visitor-plugin-common/src/utils.ts +++ b/packages/plugins/other/visitor-plugin-common/src/utils.ts @@ -136,7 +136,7 @@ export class DeclarationBlock { _content = null; _block = null; _nameGenerics = null; - _comment = null; + _comment: string | null = null; _ignoreBlockWrapper = false; constructor(private _config: DeclarationBlockConfig) { @@ -168,10 +168,13 @@ export class DeclarationBlock { return this; } - withComment(comment: string | StringValueNode | null, disabled = false): DeclarationBlock { + withComment( + comment: string | StringValueNode | null | undefined, + disabled = false, + ): DeclarationBlock { const nonEmptyComment = !!(isStringValueNode(comment) ? comment.value : comment); - if (nonEmptyComment && !disabled) { + if (comment && nonEmptyComment && !disabled) { this._comment = transformComment(comment, 0); } diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index ca62af89af9..104af573661 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -19,7 +19,7 @@ import { TypeScriptDocumentsVisitor } from './visitor.js'; export const plugin: PluginFunction< TypeScriptDocumentsPluginConfig, Types.ComplexPluginOutput -> = async (inputSchema, rawDocuments, config, { outputFile }) => { +> = async (inputSchema, rawDocuments, config, { outputFile = '' } = {}) => { const schema = config.nullability?.errorHandlingClient ? await semanticToStrict(inputSchema) : inputSchema; @@ -41,6 +41,10 @@ export const plugin: PluginFunction< }; }>( (prev, document) => { + if (!document.document) { + return prev; + } + prev.all.documentFiles.push(document); prev.all.documentNodes.push(document.document); @@ -83,7 +87,7 @@ export const plugin: PluginFunction< const operationsDefinitions: string[] = operationsResult.definitions as string[]; if (config.addOperationExport) { for (const d of allDocumentsAST.definitions) { - if ('name' in d) { + if ('name' in d && d.name) { operationsDefinitions.push( `export declare const ${d.name.value}: import("graphql").DocumentNode;`, ); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 101638e8eff..99669d0bdb3 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -618,8 +618,8 @@ export namespace Types { export type ComplexPluginOutput> = { content: string; - prepend?: string[]; - append?: string[]; + prepend?: Array; + append?: Array; meta?: M; }; export type PluginOutput = string | ComplexPluginOutput; From dd2b7c53352ce6116b6557b66d15a2f2910c5635 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 30 Sep 2026 20:06:49 +1000 Subject: [PATCH 2/3] Keep pnpm-lock.yaml in sync on Dependabot PRs and stop needless range bumps (#11010) * Improve Dependabot handling of ranges and pnpm lockfile - Use `versioning-strategy: increase-if-necessary` for published packages, so ranges like `^1.7.3` stay as-is when the new version already satisfies them and no needless release is triggered. - Add a workflow that regenerates pnpm-lock.yaml on Dependabot PRs. Jobs that run from a workspace member directory (e.g. /dev-test/*, /examples/*) only update that package.json and leave the root lockfile stale. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014AYHoZNAb9DLJFDqYb9Pr6 * Use latest actions * Fix dependabot-lockfile checkout and pnpm setup - Check out the Dependabot branch (`github.head_ref`) instead of the PR merge commit, so the rebase and push don't carry master's commits onto it. - Skip pnpm/setup's built-in install: in CI it runs frozen and fails on the stale lockfile this workflow is meant to fix. - Pin both actions by SHA, like the other workflows. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014AYHoZNAb9DLJFDqYb9Pr6 * Update pnpm/setup to v3.0.0 v3 also installs Node from .nvmrc. The `install` and `version` inputs are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014AYHoZNAb9DLJFDqYb9Pr6 --------- Co-authored-by: Claude --- .github/dependabot.yml | 3 ++ .github/workflows/dependabot-lockfile.yml | 45 +++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/workflows/dependabot-lockfile.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dce47990860..76d7a951a58 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,9 @@ updates: - '/packages/plugins/*/*' - '/packages/presets/*' - '/packages/utils/*' + # Keep ranges like `^1.7.3` when the new version already satisfies them: only the lockfile moves. + # Raising the floor would force a needless release of the published package. + versioning-strategy: increase-if-necessary schedule: interval: weekly day: monday diff --git a/.github/workflows/dependabot-lockfile.yml b/.github/workflows/dependabot-lockfile.yml new file mode 100644 index 00000000000..3a9d18655c2 --- /dev/null +++ b/.github/workflows/dependabot-lockfile.yml @@ -0,0 +1,45 @@ +name: dependabot-lockfile +on: + pull_request: + branches: + - master + paths: + - '**/package.json' + +permissions: {} + +jobs: + # Dependabot jobs that run from a workspace member directory (e.g. /dev-test/*, /examples/*) + # update that package.json but not the root pnpm-lock.yaml. Regenerate it and push it back. + update-lockfile: + name: Update pnpm-lock.yaml + if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + runs-on: ubuntu-latest + permissions: + contents: write # allows pushing the lockfile to the Dependabot branch + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.head_ref }} # the Dependabot branch, not the PR merge commit + - name: Install pnpm and Node + uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0 + with: + install: false # a frozen install would fail on the stale lockfile + - name: Update lockfile + run: pnpm install --lockfile-only --no-frozen-lockfile --ignore-scripts + - name: Commit and push + env: + HEAD_REF: ${{ github.head_ref }} + run: | + if git diff --quiet -- pnpm-lock.yaml; then + echo "pnpm-lock.yaml is up to date" + exit 0 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add pnpm-lock.yaml + git commit -m 'chore(deps): update pnpm-lock.yaml' + # Other workflows (e.g. changesets-dependencies) may push to the same branch + git pull --rebase origin "$HEAD_REF" + git push origin "HEAD:$HEAD_REF" From ad49090b4e7059afc2dc039aaaba24395a65ac35 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 30 Sep 2026 20:43:59 +1000 Subject: [PATCH 3/3] [fragment-matcher] fix: satisfy strict mode in fragment-matcher (#4683) (#11012) * [fragment-matcher] fix: satisfy strict mode in fragment-matcher (#4683) Fixes the 13 errors `plugins/other/fragment-matcher` hits once the root tsconfig enables `strict: true`, with types-only changes: - read the optional plugin `info` argument as `info?.outputFile` (TS18048, TS2345) - type the introspection result as `ExecutionResult` and annotate `createPossibleTypesCollection`, so the filter/sort/reduce callbacks are no longer implicit `any` (TS7006) - type `typename` and `type` as `string | undefined`, since they are only assigned for `apolloClientVersion` 2 or 3 (TS2454) Generated output is unchanged. The strict typecheck goes from 283 to 270 errors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MzUSkE6kGAfWan8VDUSkNt eddeee888:oss:issue-fix * Update based on preferences --------- Co-authored-by: Claude --- .../other/fragment-matcher/src/index.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/plugins/other/fragment-matcher/src/index.ts b/packages/plugins/other/fragment-matcher/src/index.ts index f60834e3c3d..e54d466f360 100644 --- a/packages/plugins/other/fragment-matcher/src/index.ts +++ b/packages/plugins/other/fragment-matcher/src/index.ts @@ -1,5 +1,5 @@ import { extname } from 'path'; -import { execute, GraphQLSchema, parse } from 'graphql'; +import { execute, GraphQLSchema, parse, type ExecutionResult } from 'graphql'; import { PluginFunction, PluginValidateFn, @@ -131,7 +131,7 @@ export const plugin: PluginFunction = async ( schema: GraphQLSchema, _documents, pluginConfig: FragmentMatcherConfig, - info, + info = {}, ): Promise => { const config: Required = { module: 'es2015', @@ -161,8 +161,8 @@ export const plugin: PluginFunction = async ( } } `), - })) as any; - const ext = extname(info.outputFile).toLowerCase(); + })) as unknown as ExecutionResult; + const ext = extname(info.outputFile || '').toLowerCase(); if (!introspection.data) { throw new Error(`Plugin "fragment-matcher" couldn't introspect the schema`); @@ -179,15 +179,6 @@ export const plugin: PluginFunction = async ( .filter(type => type.kind === 'UNION' || type.kind === 'INTERFACE') .sort((a, b) => sortStringsLexicographically(a.name, b.name)); - const createPossibleTypesCollection = (acc, type) => { - return { - ...acc, - [type.name]: type.possibleTypes - .map(possibleType => possibleType.name) - .sort(sortStringsLexicographically), - }; - }; - const filteredData: IntrospectionResultData | PossibleTypesResultData = apolloClientVersion === 2 ? { @@ -197,7 +188,16 @@ export const plugin: PluginFunction = async ( }, } : { - possibleTypes: unionAndInterfaceTypes.reduce(createPossibleTypesCollection, {}), + possibleTypes: unionAndInterfaceTypes.reduce((acc, type) => { + return { + ...acc, + // Unions and interfaces always have possibleTypes + // This `|| []` is only to satisfy type safety + [type.name]: (type.possibleTypes || []) + .map(possibleType => possibleType.name) + .sort(sortStringsLexicographically), + }; + }, {}), }; const content = JSON.stringify(filteredData, null, 2); @@ -216,14 +216,14 @@ export const plugin: PluginFunction = async ( } if (extensions.ts.includes(ext)) { - let typename: string; + let typename: string | undefined; if (apolloClientVersion === 2) { typename = `IntrospectionResultData`; } else if (apolloClientVersion === 3) { typename = `PossibleTypesResultData`; } - let type: string; + let type: string | undefined; if (useExplicitTyping) { type = `export type ${typename} = ${content};`; } else if (apolloClientVersion === 2) {