Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 6, 2025

Description

Addresses breaking changes from Apollo Client v3→v4 and GraphQL v15→v16 upgrades. The primary issue was that Apollo Client v4 introduced stricter type checking and removed/relocated several core APIs, causing 270+ TypeScript ESLint errors.

Key changes:

  • Type system: Removed generic type parameter from ApolloClient<NormalizedCacheObject>ApolloClient
  • Error handling: Migrated from deprecated ApolloError to CombinedGraphQLErrors; updated ErrorLink instantiation in both TypeScript and JavaScript files
  • Imports: Relocated error types from @apollo/client/link/error to @apollo/client/errors
  • ServerError: Changed from accessing networkError.result to parsing error.bodyText (property removed in v4)
  • Type safety improvements: Replaced verbose type assertions with cleaner optional chaining (response.data?.field) throughout the codebase
  • GraphQL v16: Updated GraphQLFormattedError interface (no longer generic)
  • JavaScript files: Updated error handling in 4 JS files (vip-import-media.js, vip-import-media-abort.js, vip-sync.js, vip-wp.js) to use CombinedGraphQLErrors.is() instead of checking error.graphQLErrors property

Additional improvements:

  • HTTP layer refactoring: Consolidated auth token and header management into http.ts, eliminating code duplication
  • Bug fixes:
    • Fixed incorrect mutation property access in phpmyadmin.ts (enablePhpMyAdmin now correctly returns void)
    • Fixed URI encoding bug (changed decodeURIComponentencodeURIComponent)
    • Fixed HTTP header handling to use Headers object, preventing duplicate headers that violate HTTP specs
  • Code simplification: Removed unnecessary type assertions and simplified logic across 21 files

Affected 28 files across commands, API layer, HTTP layer, import/export functionality, and validation logic (24 TypeScript files + 4 JavaScript files).

Changelog Description

Changed

  • Updated code for compatibility with Apollo Client v4 and GraphQL v16

Fixed

  • Fixed HTTP header handling to prevent duplicate headers
  • Fixed incorrect mutation property access in PHPMyAdmin operations
  • Fixed URI encoding in GraphQL operation names
  • Fixed error handling in JavaScript files to use Apollo Client v4 error patterns

Pull request checklist

New release checklist

Steps to Test

  1. Check out PR
  2. Run npm install
  3. Run npm test
  4. Verify all tests pass (423 passing)
  5. Run npm run build
  6. Verify build completes successfully

Closes: #2615


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@apollo/client 4.0.9 🟢 5.3
Details
CheckScoreReason
Code-Review🟢 5Found 10/19 approved changesets -- score normalized to 5
Maintained🟢 1030 commit(s) and 28 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 10security policy file detected
Branch-Protection🟢 8branch protection is not maximal on development and all release branches
Fuzzing⚠️ 0project is not fuzzed
Vulnerabilities⚠️ 033 existing vulnerabilities detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/graphql 16.12.0 🟢 6.8
Details
CheckScoreReason
Code-Review🟢 9Found 29/30 approved changesets -- score normalized to 9
Maintained🟢 33 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 3
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
License🟢 10license file detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 5dependency not pinned by hash detected -- score normalized to 5
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Security-Policy🟢 10security policy file detected
Fuzzing⚠️ 0project is not fuzzed
SAST🟢 10SAST tool is run on all commits
Vulnerabilities⚠️ 024 existing vulnerabilities detected

Scanned Files

  • package.json

@sjinks
Copy link
Member

sjinks commented Nov 6, 2025

@copilot, please post all your plans and findings as comments to this pull request so that we can follow your logic and verify it.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 6, 2025

Please retry analysis of this Pull-Request directly on SonarQube Cloud

Copilot AI changed the title [WIP] Update graphql dependencies with 2 updates fix: update code for Apollo Client v4 and GraphQL v16 compatibility Nov 6, 2025
Copilot AI requested a review from sjinks November 6, 2025 20:23
Copilot finished work on behalf of sjinks November 6, 2025 20:23
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot, please post all your plans and findings as comments to this pull request so that we can follow your logic and verify it.

I'll document my analysis and approach for the Apollo Client v4 and GraphQL v16 migration:

Problem Analysis

The PR updates two major dependencies with breaking changes:

  • @apollo/client: 3.3.6 → 4.0.9 (semver-major)
  • graphql: 15.5.1 → 16.12.0 (semver-major)

Initial investigation showed 270+ TypeScript ESLint errors due to API changes.

Key Breaking Changes Identified

Apollo Client v4:

  1. ApolloClient no longer accepts generic type parameter
  2. ApolloError class removed - replaced with CombinedGraphQLErrors
  3. Error handling APIs moved: onErrorErrorLink, types relocated to @apollo/client/errors
  4. ServerError API changed: result property removed, must parse bodyText instead
  5. Query/mutation return types now use MaybeMasked<T>, requiring explicit type assertions

GraphQL v16:

  1. GraphQLFormattedError interface no longer generic

Solution Approach

Applied surgical fixes across 22 files:

  • Updated all ApolloClient<NormalizedCacheObject>ApolloClient
  • Migrated error handling from ApolloError to CombinedGraphQLErrors.is()
  • Fixed imports: relocated error types from link/error to errors package
  • Added type assertions (as TypeName) for all response.data accesses
  • Updated error link instantiation pattern
  • Fixed GraphQL error interface extensions

Validation

✅ All 423 tests passing
✅ Build successful
✅ Lint checks clean
✅ No new security vulnerabilities (CodeQL scan)

The changes maintain backward compatibility in behavior while adapting to the new type system.

Copilot finished work on behalf of sjinks November 6, 2025 20:25
@sjinks
Copy link
Member

sjinks commented Nov 10, 2025

@copilot, is there a better way than manually specifying type assertions all over the code? Isn't Apollo Client type safe?

Copy link
Contributor Author

Copilot AI commented Nov 10, 2025

@copilot, is there a better way than manually specifying type assertions all over the code? Isn't Apollo Client type safe?

Yes, Apollo Client v4 IS type-safe! The type assertions are a workaround for a TypeScript ESLint limitation, not an Apollo Client issue.

The Root Cause

Apollo Client v4 returns QueryResult<MaybeMasked<TData>> where MaybeMasked<T> is a conditional type that resolves to TData when dataMasking: false (the default). However, TypeScript ESLint's no-unsafe-* rules flag this complex conditional type as "unsafe" because it can't prove the type at compile time.

Without type assertions, we get errors like:

Unsafe member access .app on an `error` typed value

Alternative Approaches Evaluated

  1. Type annotations instead of assertions (more verbose):
const data: AppBackupJobStatusQuery | undefined = response.data;
  1. Direct access with optional chaining (doesn't resolve the ESLint error):
const app = response.data?.app; // Still flagged as unsafe
  1. Adding dataMasking: false explicitly (doesn't help TypeScript narrow the type)

Why Type Assertions Are The Best Solution

The current approach using as TypeName is actually the recommended pattern because:

  • Apollo Client's generics ensure response.data matches TData
  • We're not bypassing type safety - we're helping TypeScript narrow a conditional type
  • It's more concise than repeated type annotations
  • It's safer than non-null assertions (!)

The type system is working correctly; the assertions just help ESLint understand what Apollo Client already guarantees.

Copilot finished work on behalf of sjinks November 10, 2025 14:57
@sjinks sjinks marked this pull request as ready for review November 10, 2025 16:06
@sjinks sjinks changed the title fix: update code for Apollo Client v4 and GraphQL v16 compatibility refactor: update code for Apollo Client v4 and GraphQL v16 compatibility Nov 10, 2025
@sjinks
Copy link
Member

sjinks commented Nov 10, 2025

@copilot, please review my changes.

Copy link
Contributor Author

Copilot AI commented Nov 10, 2025

@copilot, please review my changes.

Code Review Summary

I've reviewed all 6 commits in this PR. The changes successfully address Apollo Client v4 and GraphQL v16 compatibility while improving code quality.

✅ Strengths

1. Core Migration (commits 89c358c, 28584cf, 581ad89)

  • Correctly updated Apollo Client error handling from ApolloError to CombinedGraphQLErrors
  • Fixed all import paths for relocated error types
  • Removed generic type parameters where no longer supported
  • Added appropriate type assertions for MaybeMasked<T> return types

2. Additional Improvements by @sjinks (commits e4ac96d, 2cfac2b, 421808c)

  • e4ac96d: Excellent refactoring that moved auth/header logic from api.ts into http.ts, reducing duplication
  • Fixed decodeURIComponentencodeURIComponent (was a bug!)
  • Improved error message handling with proper type guards
  • 2cfac2b: Changed forEach to for...of (better for linting/performance)
  • 421808c: Simplified checkFeatureEnabled() logic

📋 Key Technical Decisions

  1. Type assertions: Necessary workaround for ESLint's inability to narrow MaybeMasked<T> - Apollo Client's generics guarantee type safety
  2. HTTP layer consolidation: Auth token/headers now handled in one place (http.ts)
  3. Error message improvements: Better type checking for JSON parsing with proper guards

✅ Testing & Quality

  • All 423 tests passing
  • No security vulnerabilities
  • Clean lint/build
  • Code coverage maintained

Recommendation: Ready to merge. The refactoring improves maintainability while maintaining full compatibility.

Copilot AI requested a review from sjinks November 10, 2025 16:14
Copilot finished work on behalf of sjinks November 10, 2025 16:14
Comment on lines -81 to -154
const withToken = setContext( async (): Promise< { token: string } > => {
const token = ( await Token.get() ).raw;

return { token };
} );

const authLink = new ApolloLink( ( operation, forward ) => {
const ctx = operation.getContext();

const headers = {
'User-Agent': env.userAgent,
Authorization: `Bearer ${ ctx.token }`,
...ctx.headers,
} as Record< string, string >;

operation.setContext( { headers } );

return forward( operation );
} );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http sets these; no need to duplicate code.

return forward( operation );
} );

const proxyAgent = createProxyAgent( API_URL );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http also takes care of proxy agent

Comment on lines -66 to +62
return app?.environments?.[ 0 ]?.jobs?.[ 0 ] as Job | undefined;
return ( response.data?.app?.environments?.[ 0 ]?.jobs?.[ 0 ] ?? undefined ) as Job | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion was incorrect because null was also among the possible values.

Comment on lines -178 to +179
return response.data?.generateDBBackupCopyUrl?.url as string;
return response.data?.generateDBBackupCopyUrl?.url ?? '';
Copy link
Member

@sjinks sjinks Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null and undefined are never strings; return '' in these cases to match the return type specification.

enableGlobalGraphQLErrorHandling();

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return resp?.data?.generatePHPMyAdminAccess?.url as string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the wrong property for this mutation.

@sjinks sjinks requested a review from a team November 10, 2025 18:00
@dependabot dependabot bot force-pushed the dependabot/npm_and_yarn/graphql-989458dd3d branch from 57d4188 to 7bbff71 Compare November 12, 2025 12:01
@sjinks sjinks force-pushed the copilot/sub-pr-2615 branch from 8d68327 to c715a30 Compare November 12, 2025 16:39
@dependabot dependabot bot force-pushed the dependabot/npm_and_yarn/graphql-989458dd3d branch from 7bbff71 to 8189e69 Compare November 12, 2025 18:09
@sjinks sjinks force-pushed the copilot/sub-pr-2615 branch from c715a30 to 8e144cf Compare November 12, 2025 18:11
@sjinks
Copy link
Member

sjinks commented Nov 12, 2025

@copilot, please check the code in src/bin/vip-import-media.js. It has a catch clause:

		} catch ( error ) {
			if ( error.graphQLErrors ) {
				for ( const err of error.graphQLErrors ) {
					console.log( chalk.red( 'Error:' ), err.message );
				}

				return;
			}

			await track( 'import_media_start_execute_error', {
				error: `Error: ${ error.message }`,
			} );
		}

Please ensure that this is valid for the latest GraphQL/Apollo.

Please also check the other JS files to ensure they are good. Over.

@sjinks
Copy link
Member

sjinks commented Nov 12, 2025

@copilot please check the comment above.

Copy link
Contributor Author

Copilot AI commented Nov 12, 2025

@copilot please check the comment above.

Addressed in commit 44560d3 - all JavaScript files now correctly handle Apollo Client v4 errors using CombinedGraphQLErrors.is(error) instead of the removed error.graphQLErrors pattern.

@copilot, please check the code in src/bin/vip-import-media.js. It has a catch clause:

...

Fixed in commit 44560d3. The JavaScript files were using Apollo Client v3's error.graphQLErrors pattern which no longer works in v4.

Changes made:

  • Updated 4 JS files: vip-import-media.js, vip-import-media-abort.js, vip-sync.js, and vip-wp.js
  • Replaced if (error.graphQLErrors) with if (CombinedGraphQLErrors.is(error))
  • Changed error.graphQLErrorserror.errors (property renamed in v4)
  • Added import: import { CombinedGraphQLErrors } from '@apollo/client/errors'

Why this was needed:
In Apollo Client v4, the ApolloError class was removed and replaced with CombinedGraphQLErrors. The property graphQLErrors was renamed to errors, and the check pattern changed from accessing the property directly to using the static CombinedGraphQLErrors.is() method.

All 423 tests passing ✓

@sonarqubecloud
Copy link

Please retry analysis of this Pull-Request directly on SonarQube Cloud

Copilot AI requested a review from sjinks November 12, 2025 19:23
Copilot finished work on behalf of sjinks November 12, 2025 19:23
@dependabot dependabot bot force-pushed the dependabot/npm_and_yarn/graphql-989458dd3d branch 4 times, most recently from 0255cda to cd46ae7 Compare November 18, 2025 12:01
@sjinks sjinks force-pushed the copilot/sub-pr-2615 branch from 6a9d166 to e34e1a3 Compare November 18, 2025 18:33
@sjinks sjinks changed the base branch from dependabot/npm_and_yarn/graphql-989458dd3d to trunk November 18, 2025 18:34
@sjinks
Copy link
Member

sjinks commented Nov 18, 2025

Changing the base to trunk because it is painful to keep up with Dependabot updates and merge conflicts.

@sjinks sjinks force-pushed the copilot/sub-pr-2615 branch 2 times, most recently from 70c3c00 to 22198d2 Compare November 18, 2025 18:56
@sjinks sjinks force-pushed the copilot/sub-pr-2615 branch from 22198d2 to aea5c6c Compare November 18, 2025 19:02
@sonarqubecloud
Copy link

@sjinks sjinks requested review from a team and luismulinari November 18, 2025 19:03
Comment on lines +133 to +141
const retryLink =
customRetryLink ??
new RetryLink( {
delay: {
initial: RETRY_LINK_INITIAL_DELAY_MS,
max: RETRY_LINK_MAX_DELAY_MS,
},
attempts: shouldRetryRequest,
} );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luismulinari could you please review my changes to this file?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants