From c0a9a3428885d29d10cef54d27c35bbbe3b39c88 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:56:56 +0000 Subject: [PATCH 1/6] fix(deps): update dependency graphql to v17 --- package-lock.json | 93 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3136cef5e60..0da0962b335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "fast-redact": "3.5.0", "get-urls": "10.0.1", "gqlmin": "0.3.2", - "graphql": "16.14.1", + "graphql": "17.0.1", "graphql-scalars": "1.25.0", "graphql-upload": "15.0.2", "handlebars": "4.7.9", @@ -2912,6 +2912,15 @@ "@envelop/core": "^5.2.3" } }, + "node_modules/@escape.tech/graphql-armor-block-field-suggestions/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-cost-limit": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-cost-limit/-/graphql-armor-cost-limit-2.4.3.tgz", @@ -2928,6 +2937,15 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, + "node_modules/@escape.tech/graphql-armor-cost-limit/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-max-aliases": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-aliases/-/graphql-armor-max-aliases-2.6.2.tgz", @@ -2944,6 +2962,15 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, + "node_modules/@escape.tech/graphql-armor-max-aliases/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-max-depth": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-depth/-/graphql-armor-max-depth-2.4.2.tgz", @@ -2960,6 +2987,15 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, + "node_modules/@escape.tech/graphql-armor-max-depth/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-max-directives": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-directives/-/graphql-armor-max-directives-2.3.1.tgz", @@ -2976,6 +3012,15 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, + "node_modules/@escape.tech/graphql-armor-max-directives/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-max-tokens": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-tokens/-/graphql-armor-max-tokens-2.5.1.tgz", @@ -2992,6 +3037,15 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, + "node_modules/@escape.tech/graphql-armor-max-tokens/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@escape.tech/graphql-armor-types": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-types/-/graphql-armor-types-0.7.0.tgz", @@ -3002,6 +3056,25 @@ "graphql": "^16.0.0" } }, + "node_modules/@escape.tech/graphql-armor-types/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/@escape.tech/graphql-armor/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -8269,6 +8342,16 @@ "graphql": "^16.3.0" } }, + "node_modules/@types/graphql-upload/node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/@types/http-assert": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", @@ -13826,12 +13909,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphql": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.1.tgz", - "integrity": "sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-17.0.1.tgz", + "integrity": "sha512-8eWbg5Zcv/8o20nzEjHUGPTj20MLFJjc5kagbIPxbaeGxvFwpitJhemEC/k17n5+UD4M/9ea5rTuce78mELujQ==", "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + "node": "^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0" } }, "node_modules/graphql-config": { diff --git a/package.json b/package.json index f33b02c708e..6b6aa78cb92 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "fast-redact": "3.5.0", "get-urls": "10.0.1", "gqlmin": "0.3.2", - "graphql": "16.14.1", + "graphql": "17.0.1", "graphql-scalars": "1.25.0", "graphql-upload": "15.0.2", "handlebars": "4.7.9", From 45e9f670c92392c7372fbaea51b3b1c1376c3397 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 07:13:27 +0000 Subject: [PATCH 2/6] fix(graphql): complete graphql v17 upgrade compatibility GraphQL v17 enforces that interface fields cannot be non-deprecated when implementation fields are deprecated. Remove location from Host fields that receive a deprecation reason since Account.location is not deprecated. Update input field defaults to use the new GraphQLDefaultInput shape ({ value: ... }) required by graphql v17. Add an npm override to dedupe nested graphql@16 packages from @escape.tech/graphql-armor, fixing ValidationRule type mismatches with Apollo Server. --- package-lock.json | 83 ------------------- package.json | 3 +- server/graphql/schemaV2.graphql | 2 +- server/graphql/v2/input/AccountUpdateInput.js | 2 +- server/graphql/v2/input/EventCreateInput.ts | 2 +- server/graphql/v2/object/Host.ts | 1 - 6 files changed, 5 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0da0962b335..3069b887bdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2912,15 +2912,6 @@ "@envelop/core": "^5.2.3" } }, - "node_modules/@escape.tech/graphql-armor-block-field-suggestions/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-cost-limit": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-cost-limit/-/graphql-armor-cost-limit-2.4.3.tgz", @@ -2937,15 +2928,6 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, - "node_modules/@escape.tech/graphql-armor-cost-limit/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-max-aliases": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-aliases/-/graphql-armor-max-aliases-2.6.2.tgz", @@ -2962,15 +2944,6 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, - "node_modules/@escape.tech/graphql-armor-max-aliases/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-max-depth": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-depth/-/graphql-armor-max-depth-2.4.2.tgz", @@ -2987,15 +2960,6 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, - "node_modules/@escape.tech/graphql-armor-max-depth/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-max-directives": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-directives/-/graphql-armor-max-directives-2.3.1.tgz", @@ -3012,15 +2976,6 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, - "node_modules/@escape.tech/graphql-armor-max-directives/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-max-tokens": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-max-tokens/-/graphql-armor-max-tokens-2.5.1.tgz", @@ -3037,15 +2992,6 @@ "@escape.tech/graphql-armor-types": "0.7.0" } }, - "node_modules/@escape.tech/graphql-armor-max-tokens/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@escape.tech/graphql-armor-types": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@escape.tech/graphql-armor-types/-/graphql-armor-types-0.7.0.tgz", @@ -3056,25 +3002,6 @@ "graphql": "^16.0.0" } }, - "node_modules/@escape.tech/graphql-armor-types/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, - "node_modules/@escape.tech/graphql-armor/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -8342,16 +8269,6 @@ "graphql": "^16.3.0" } }, - "node_modules/@types/graphql-upload/node_modules/graphql": { - "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@types/http-assert": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", diff --git a/package.json b/package.json index 6b6aa78cb92..9665f550b01 100644 --- a/package.json +++ b/package.json @@ -279,7 +279,8 @@ ] }, "overrides": { - "url-regex-safe": "4.0.0" + "url-regex-safe": "4.0.0", + "graphql": "17.0.1" }, "nyc": { "reporter": [ diff --git a/server/graphql/schemaV2.graphql b/server/graphql/schemaV2.graphql index 1688e59607a..301a258fc17 100644 --- a/server/graphql/schemaV2.graphql +++ b/server/graphql/schemaV2.graphql @@ -3890,7 +3890,7 @@ type Host implements Account & AccountWithContributions & AccountWithPlatformSub ): OAuthApplicationCollection "\n Address. This field is public for hosts, otherwise:\n - Users can see the addresses of the collectives they're admin of; if they are not an admin they can only see the country that the org belong to.\n - Hosts can see the address of organizations submitting expenses to their collectives.\n " - location: Location @deprecated(reason: "2026-04-22: This field has moved to the Organization type") + location: Location categories: [String]! stats: AccountStats diff --git a/server/graphql/v2/input/AccountUpdateInput.js b/server/graphql/v2/input/AccountUpdateInput.js index cb95a0b3c7d..e7abd576515 100644 --- a/server/graphql/v2/input/AccountUpdateInput.js +++ b/server/graphql/v2/input/AccountUpdateInput.js @@ -68,7 +68,7 @@ export const GraphQLAccountUpdateInput = new GraphQLInputObjectType({ timezone: { description: 'Timezone of the Event (TZ database format, e.g. UTC or Europe/Berlin)', type: GraphQLString, - default: 'UTC', + default: { value: 'UTC' }, }, privateInstructions: { type: GraphQLString, diff --git a/server/graphql/v2/input/EventCreateInput.ts b/server/graphql/v2/input/EventCreateInput.ts index 5185b6b2318..616b9bf88f7 100644 --- a/server/graphql/v2/input/EventCreateInput.ts +++ b/server/graphql/v2/input/EventCreateInput.ts @@ -25,7 +25,7 @@ export const GraphQLEventCreateInput = new GraphQLInputObjectType({ timezone: { description: 'Timezone of the Event (TZ database format, e.g. UTC or Europe/Berlin)', type: new GraphQLNonNull(GraphQLString), - default: 'UTC', + default: { value: 'UTC' }, }, ...AccountImagesInputFields, }), diff --git a/server/graphql/v2/object/Host.ts b/server/graphql/v2/object/Host.ts index b8443c48739..d5e449cb5f4 100644 --- a/server/graphql/v2/object/Host.ts +++ b/server/graphql/v2/object/Host.ts @@ -1506,7 +1506,6 @@ export const GraphQLHost = new GraphQLObjectType({ }, ...mapValues( pick(getOrganizationFields(), [ - 'location', 'accountingCategories', 'contributionAccountingCategoryRules', 'hasMoneyManagement', From 5c6fe2abbfc388b4ea7cce40559ab845c1970352 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 07:52:49 +0000 Subject: [PATCH 3/6] fix(graphql): align CollectiveInterface deprecation with implementations GraphQL v17 requires interface fields to be deprecated when implementation fields are. Add deprecation reasons to tiers, stats, and plan on the CollectiveInterface type to match CollectiveFields. --- server/graphql/schemaV1.graphql | 6 +++--- server/graphql/v1/CollectiveInterface.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/graphql/schemaV1.graphql b/server/graphql/schemaV1.graphql index d1823cf9968..f4e466fd491 100644 --- a/server/graphql/schemaV1.graphql +++ b/server/graphql/schemaV1.graphql @@ -131,9 +131,9 @@ interface CollectiveInterface { When true (default), exclude tiers with types that are no longer supported. Use false for edit pages to show all tiers. """ onlyValid: Boolean = true - ): [Tier] + ): [Tier] @deprecated(reason: "2025-12-05: Will be deleted soon. Use GraphQL v2") orders(status: OrderStatus): [OrderType] - stats: CollectiveStatsType + stats: CollectiveStatsType @deprecated(reason: "2025-07-10: Please use GraphQL V2") """ The list of expense types supported by this account @@ -200,7 +200,7 @@ interface CollectiveInterface { Describes the features enabled and available for this collective """ features: CollectiveFeatures! - plan: PlanType + plan: PlanType @deprecated(reason: "2026-04-02: Replaced by new pricing") contributionPolicy: String """ diff --git a/server/graphql/v1/CollectiveInterface.js b/server/graphql/v1/CollectiveInterface.js index f3d31ac14c8..6d4f5c4df10 100644 --- a/server/graphql/v1/CollectiveInterface.js +++ b/server/graphql/v1/CollectiveInterface.js @@ -350,6 +350,7 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({ }, tiers: { type: new GraphQLList(TierType), + deprecationReason: '2025-12-05: Will be deleted soon. Use GraphQL v2', args: { id: { type: GraphQLInt }, slug: { type: GraphQLString }, @@ -368,7 +369,10 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({ status: { type: OrderStatusType }, }, }, - stats: { type: CollectiveStatsType }, + stats: { + type: CollectiveStatsType, + deprecationReason: '2025-07-10: Please use GraphQL V2', + }, supportedExpenseTypes: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))), description: 'The list of expense types supported by this account', @@ -448,7 +452,10 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({ type: new GraphQLNonNull(GraphQLCollectiveFeatures), description: 'Describes the features enabled and available for this collective', }, - plan: { type: PlanType }, + plan: { + type: PlanType, + deprecationReason: '2026-04-02: Replaced by new pricing', + }, contributionPolicy: { type: GraphQLString }, categories: { type: new GraphQLNonNull(new GraphQLList(GraphQLString)), From eae66ec22068b7869b280099f5e20c6b06a7fca5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 08:16:32 +0000 Subject: [PATCH 4/6] test(graphql): update missing variable error expectations for v17 GraphQL v17 reports missing required variables as invalid values rather than using the previous "was not provided" message format. --- .../graphql/v2/mutation/HostApplicationMutations.test.ts | 2 +- test/server/graphql/v2/mutation/RootMutations.test.ts | 2 +- test/server/paymentProviders/opencollective/giftcard.test.js | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/server/graphql/v2/mutation/HostApplicationMutations.test.ts b/test/server/graphql/v2/mutation/HostApplicationMutations.test.ts index b8efee5e34e..c5186f11cc5 100644 --- a/test/server/graphql/v2/mutation/HostApplicationMutations.test.ts +++ b/test/server/graphql/v2/mutation/HostApplicationMutations.test.ts @@ -818,7 +818,7 @@ describe('server/graphql/v2/mutation/HostApplicationMutations', () => { const result = await graphqlQueryV2(REMOVE_HOST_MUTATION, {}); expect(result.errors).to.exist; expect(result.errors[0].message).to.equal( - 'Variable "$account" of required type "AccountReferenceInput!" was not provided.', + 'Variable "$account" has invalid value: Expected a value of non-null type "AccountReferenceInput!" to be provided.', ); }); diff --git a/test/server/graphql/v2/mutation/RootMutations.test.ts b/test/server/graphql/v2/mutation/RootMutations.test.ts index b683dade8db..e2969e2d1c1 100644 --- a/test/server/graphql/v2/mutation/RootMutations.test.ts +++ b/test/server/graphql/v2/mutation/RootMutations.test.ts @@ -88,7 +88,7 @@ describe('server/graphql/v2/mutation/RootMutations', () => { const result = await callMoveExpenseMutation({ expenses: [] }, rootUser); expect(result.errors).to.exist; expect(result.errors[0].message).to.equal( - 'Variable "$destinationAccount" of required type "AccountReferenceInput!" was not provided.', + 'Variable "$destinationAccount" has invalid value: Expected a value of non-null type "AccountReferenceInput!" to be provided.', ); }); diff --git a/test/server/paymentProviders/opencollective/giftcard.test.js b/test/server/paymentProviders/opencollective/giftcard.test.js index a2520263bda..7f3adae730a 100644 --- a/test/server/paymentProviders/opencollective/giftcard.test.js +++ b/test/server/paymentProviders/opencollective/giftcard.test.js @@ -783,7 +783,9 @@ describe('server/paymentProviders/opencollective/giftcard', () => { // call graphql mutation const gqlResult = await utils.graphqlQuery(createGiftCardsMutation, args, user1); expect(gqlResult.errors[0]).to.exist; - expect(gqlResult.errors[0].toString()).to.contain('"$currency" of required type "String!" was not provided.'); + expect(gqlResult.errors[0].message).to.contain( + 'Variable "$currency" has invalid value: Expected a value of non-null type "String!" to be provided.', + ); }); /** End of "should fail creating a gift card because there is no currency defined" */ it('should fail creating a gift card because there is no amount or monthlyLimitPerMember defined', async () => { From a4724de692988156af01d7f2434012a8d9cbacb9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 08:32:33 +0000 Subject: [PATCH 5/6] test(graphql): update invalid variable error expectations for v17 GraphQL v17 uses "has invalid value" instead of "got invalid value" when reporting variable coercion failures. --- test/server/graphql/v2/mutation/AccountMutations.test.ts | 2 +- .../v2/query/collection/ExpensesCollectionQuery.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/server/graphql/v2/mutation/AccountMutations.test.ts b/test/server/graphql/v2/mutation/AccountMutations.test.ts index f4a82584b17..b4a07979995 100644 --- a/test/server/graphql/v2/mutation/AccountMutations.test.ts +++ b/test/server/graphql/v2/mutation/AccountMutations.test.ts @@ -256,7 +256,7 @@ describe('server/graphql/v2/mutation/AccountMutations', () => { ); expect(result.errors).to.exist; - expect(result.errors[0].message).to.match(/Variable "\$key" got invalid value "anInvalidKey\!"/); + expect(result.errors[0].message).to.match(/Variable "\$key" has invalid value.*anInvalidKey!/); }); it('can set nested values', async () => { diff --git a/test/server/graphql/v2/query/collection/ExpensesCollectionQuery.test.ts b/test/server/graphql/v2/query/collection/ExpensesCollectionQuery.test.ts index 6b62c4a2146..cd4ce03c471 100644 --- a/test/server/graphql/v2/query/collection/ExpensesCollectionQuery.test.ts +++ b/test/server/graphql/v2/query/collection/ExpensesCollectionQuery.test.ts @@ -1404,7 +1404,7 @@ describe('server/graphql/v2/collection/ExpenseCollection', () => { const result = await graphqlQueryV2(expensesQuery, queryParams); expect(result.errors).to.exist; // GraphQL validation error happens before resolver - expect(result.errors[0].message).to.include('Variable "$activity" got invalid value'); + expect(result.errors[0].message).to.include('Variable "$activity" has invalid value'); }); it('Throws error when multiple invalid activity types are provided', async () => { @@ -1415,7 +1415,7 @@ describe('server/graphql/v2/collection/ExpenseCollection', () => { const result = await graphqlQueryV2(expensesQuery, queryParams); expect(result.errors).to.exist; // GraphQL validation error happens before resolver - expect(result.errors[0].message).to.include('Variable "$activity" got invalid value'); + expect(result.errors[0].message).to.include('Variable "$activity" has invalid value'); }); it('Throws error when activity type is not an expense-related activity', async () => { From c54e28f23f5de20a9742b0eb418826e1e5846183 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 23 Jun 2026 08:15:40 +0000 Subject: [PATCH 6/6] fix(graphql): migrate collection arg defaults to v17 format GraphQL v17 requires argument defaults to use the default: { value } shape for VariablesInAllowedPositionRule to recognize them. This fixes E2E failures where nullable $limit/$offset variables were rejected against Int! collection arguments (e.g. DashboardVendors query). --- .../v2/collection/WebhookCollection.ts | 12 ++++-- .../v2/interface/AccountWithContributions.ts | 8 +--- server/graphql/v2/interface/Collection.ts | 39 ++++++++++--------- .../collection/ActivitiesCollectionQuery.ts | 4 +- .../v2/query/collection/CommunityQuery.ts | 7 ++-- .../query/collection/OrdersCollectionQuery.ts | 4 +- .../TransactionGroupCollectionQuery.ts | 4 +- .../collection/TransactionsCollectionQuery.ts | 4 +- 8 files changed, 43 insertions(+), 39 deletions(-) diff --git a/server/graphql/v2/collection/WebhookCollection.ts b/server/graphql/v2/collection/WebhookCollection.ts index f4df177c175..9e5399e5eab 100644 --- a/server/graphql/v2/collection/WebhookCollection.ts +++ b/server/graphql/v2/collection/WebhookCollection.ts @@ -5,7 +5,13 @@ import models from '../../../models'; import { checkRemoteUserCanUseWebhooks } from '../../common/scope-check'; import { Forbidden } from '../../errors'; import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../input/AccountReferenceInput'; -import { CollectionArgs, CollectionFields, GraphQLCollection } from '../interface/Collection'; +import { + COLLECTION_DEFAULT_LIMIT, + COLLECTION_DEFAULT_OFFSET, + CollectionArgs, + CollectionFields, + GraphQLCollection, +} from '../interface/Collection'; import { GraphQLWebhook } from '../object/Webhook'; export const GraphQLWebhookCollection = new GraphQLObjectType({ @@ -33,10 +39,10 @@ export const WebhookCollectionResolver = async (args, req: express.Request) => { // Check Pagination arguments if (args.limit <= 0) { - args.limit = CollectionArgs.limit.defaultValue; + args.limit = COLLECTION_DEFAULT_LIMIT; } if (args.offset <= 0) { - args.offset = CollectionArgs.offset.defaultValue; + args.offset = COLLECTION_DEFAULT_OFFSET; } // Check and Fetch account diff --git a/server/graphql/v2/interface/AccountWithContributions.ts b/server/graphql/v2/interface/AccountWithContributions.ts index 6904e48d61b..a97e3fd9c90 100644 --- a/server/graphql/v2/interface/AccountWithContributions.ts +++ b/server/graphql/v2/interface/AccountWithContributions.ts @@ -22,7 +22,7 @@ import { GraphQLContributorCollection } from '../collection/ContributorCollectio import { GraphQLTierCollection } from '../collection/TierCollection'; import { GraphQLAccountType, GraphQLMemberRole, GraphQLTierType } from '../enum'; -import { CollectionArgs } from './Collection'; +import { CollectionArgs, collectionLimitArg } from './Collection'; export const AccountWithContributionsFields = { totalFinancialContributors: { @@ -53,11 +53,7 @@ export const AccountWithContributionsFields = { type: new GraphQLNonNull(GraphQLTierCollection), args: { ...CollectionArgs, - limit: { - type: new GraphQLNonNull(GraphQLInt), - description: 'The number of results to fetch', - defaultValue: 100, - }, + limit: collectionLimitArg(100), onlyValid: { type: GraphQLBoolean, description: diff --git a/server/graphql/v2/interface/Collection.ts b/server/graphql/v2/interface/Collection.ts index bdbf5c829d4..f8f491ac131 100644 --- a/server/graphql/v2/interface/Collection.ts +++ b/server/graphql/v2/interface/Collection.ts @@ -30,35 +30,36 @@ const GraphQLCollection = new GraphQLInterfaceType({ fields: () => CollectionFields, }); +export const COLLECTION_DEFAULT_LIMIT = 10; +export const COLLECTION_DEFAULT_OFFSET = 0; + +export const collectionLimitArg = (defaultLimit = COLLECTION_DEFAULT_LIMIT) => ({ + type: new GraphQLNonNull(GraphQLInt), + description: 'The number of results to fetch (default 10, max 1000)', + default: { value: defaultLimit }, +}); + +export const collectionOffsetArg = (defaultOffset = COLLECTION_DEFAULT_OFFSET) => ({ + type: new GraphQLNonNull(GraphQLInt), + description: 'The offset to use to fetch', + default: { value: defaultOffset }, +}); + /** * Types to use as arguments for fields that return types * that implement the Collection interface. */ const CollectionArgs = { - limit: { - type: new GraphQLNonNull(GraphQLInt), - description: 'The number of results to fetch (default 10, max 1000)', - defaultValue: 10, - }, - offset: { - type: new GraphQLNonNull(GraphQLInt), - description: 'The offset to use to fetch', - defaultValue: 0, - }, + limit: collectionLimitArg(), + offset: collectionOffsetArg(), }; /** * A helper to return `CollectionArgs` with custom defaults */ -export const getCollectionArgs = ({ limit = 10, offset = 0 }) => ({ - limit: { - ...CollectionArgs.limit, - defaultValue: limit ?? CollectionArgs.limit.defaultValue, - }, - offset: { - ...CollectionArgs.offset, - defaultValue: offset ?? CollectionArgs.offset.defaultValue, - }, +export const getCollectionArgs = ({ limit = COLLECTION_DEFAULT_LIMIT, offset = COLLECTION_DEFAULT_OFFSET } = {}) => ({ + limit: collectionLimitArg(limit ?? COLLECTION_DEFAULT_LIMIT), + offset: collectionOffsetArg(offset ?? COLLECTION_DEFAULT_OFFSET), }); export interface CollectionReturnType { diff --git a/server/graphql/v2/query/collection/ActivitiesCollectionQuery.ts b/server/graphql/v2/query/collection/ActivitiesCollectionQuery.ts index 76625d58c48..4c0d0a9d279 100644 --- a/server/graphql/v2/query/collection/ActivitiesCollectionQuery.ts +++ b/server/graphql/v2/query/collection/ActivitiesCollectionQuery.ts @@ -24,12 +24,12 @@ import { CHRONOLOGICAL_ORDER_INPUT_DEFAULT_VALUE, GraphQLChronologicalOrderInput, } from '../../input/ChronologicalOrderInput'; -import { CollectionArgs, CollectionReturnType } from '../../interface/Collection'; +import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection'; const IGNORED_ACTIVITIES: string[] = [ActivityTypes.COLLECTIVE_TRANSACTION_CREATED]; // This activity is creating a lot of noise, is usually covered already by orders/expenses activities and is not properly categorized (see https://github.com/opencollective/opencollective/issues/5903) const ActivitiesCollectionArgs = { - limit: { ...CollectionArgs.limit, defaultValue: 100 }, + limit: collectionLimitArg(100), offset: CollectionArgs.offset, individual: { type: GraphQLAccountReferenceInput, diff --git a/server/graphql/v2/query/collection/CommunityQuery.ts b/server/graphql/v2/query/collection/CommunityQuery.ts index e5055f4721e..d16c4419799 100644 --- a/server/graphql/v2/query/collection/CommunityQuery.ts +++ b/server/graphql/v2/query/collection/CommunityQuery.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import type Express from 'express'; -import { GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql'; +import { GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql'; import { sql } from 'kysely'; import { isNil } from 'lodash'; @@ -18,6 +18,7 @@ import { GraphQLCommunityRelationType } from '../../enum/CommunityRelationType'; import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../../input/AccountReferenceInput'; import { getAmountRangeQuery, GraphQLAmountRangeInput } from '../../input/AmountRangeInput'; import { GraphQLOrderByInput } from '../../input/OrderByInput'; +import { collectionLimitArg, collectionOffsetArg } from '../../interface/Collection'; const DEFAULT_LIMIT = 100; @@ -255,8 +256,8 @@ const CommunityQuery = { type: GraphQLAmountRangeInput, description: 'Only return accounts that expended within this amount range', }, - limit: { type: new GraphQLNonNull(GraphQLInt), defaultValue: DEFAULT_LIMIT }, - offset: { type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, + limit: collectionLimitArg(DEFAULT_LIMIT), + offset: collectionOffsetArg(0), }, async resolve(_: void, args, req: Express.Request) { enforceScope(req, 'host'); diff --git a/server/graphql/v2/query/collection/OrdersCollectionQuery.ts b/server/graphql/v2/query/collection/OrdersCollectionQuery.ts index 85aa3d6c3ea..4b21b59218c 100644 --- a/server/graphql/v2/query/collection/OrdersCollectionQuery.ts +++ b/server/graphql/v2/query/collection/OrdersCollectionQuery.ts @@ -52,7 +52,7 @@ import { GraphQLPaymentMethodReferenceInput, } from '../../input/PaymentMethodReferenceInput'; import { getDatabaseIdFromTierReference, GraphQLTierReferenceInput } from '../../input/TierReferenceInput'; -import { CollectionArgs, CollectionReturnType } from '../../interface/Collection'; +import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection'; import { UncategorizedValue } from '../../object/AccountingCategory'; /** @@ -153,7 +153,7 @@ const getCollectivesCondition = ( }; export const OrdersCollectionArgs = { - limit: { ...CollectionArgs.limit, defaultValue: 100 }, + limit: collectionLimitArg(100), offset: CollectionArgs.offset, accountingCategory: { type: new GraphQLList(GraphQLString), diff --git a/server/graphql/v2/query/collection/TransactionGroupCollectionQuery.ts b/server/graphql/v2/query/collection/TransactionGroupCollectionQuery.ts index d5dc9298dfd..2066ad30a22 100644 --- a/server/graphql/v2/query/collection/TransactionGroupCollectionQuery.ts +++ b/server/graphql/v2/query/collection/TransactionGroupCollectionQuery.ts @@ -13,12 +13,12 @@ import { GraphQLTransactionGroupCollection } from '../../collection/TransactionG import { GraphQLTransactionKind } from '../../enum/TransactionKind'; import { GraphQLTransactionType } from '../../enum/TransactionType'; import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../../input/AccountReferenceInput'; -import { CollectionArgs, CollectionReturnType } from '../../interface/Collection'; +import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection'; import { getTransactionKindPriorityCase } from './TransactionsCollectionQuery'; export const TransactionGroupCollectionArgs = { - limit: { ...CollectionArgs.limit, defaultValue: 100 }, + limit: collectionLimitArg(100), offset: CollectionArgs.offset, type: { type: GraphQLTransactionType, diff --git a/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts b/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts index efe79e7641d..ebbc834106c 100644 --- a/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts +++ b/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts @@ -57,7 +57,7 @@ import { GraphQLPayoutMethodReferenceInput, } from '../../input/PayoutMethodReferenceInput'; import { GraphQLVirtualCardReferenceInput } from '../../input/VirtualCardReferenceInput'; -import { CollectionArgs } from '../../interface/Collection'; +import { CollectionArgs, collectionLimitArg } from '../../interface/Collection'; const oneDayInSeconds = 60 * 60 * 24; @@ -84,7 +84,7 @@ export const getTransactionKindPriorityCase = tableName => ` END`; export const TransactionsCollectionArgs = { - limit: { ...CollectionArgs.limit, defaultValue: 100 }, + limit: collectionLimitArg(100), offset: CollectionArgs.offset, type: { type: GraphQLTransactionType,