├── .husky └── .gitignore ├── .npmrc ├── .prettierignore ├── website ├── postcss.config.js ├── src │ ├── mdx-components.js │ ├── content │ │ ├── user │ │ │ ├── _meta.ts │ │ │ ├── documents.mdx │ │ │ ├── schema.mdx │ │ │ └── usage.mdx │ │ ├── installation.mdx │ │ ├── _meta.ts │ │ ├── library │ │ │ ├── _meta.ts │ │ │ ├── loaders.mdx │ │ │ ├── graphql-config.mdx │ │ │ ├── load-config.mdx │ │ │ ├── graphql-project-config.mdx │ │ │ └── extensions.mdx │ │ ├── migration.mdx │ │ └── index.mdx │ └── app │ │ ├── _meta.ts │ │ ├── giscus.tsx │ │ ├── changelog │ │ └── page.mdx │ │ ├── docs │ │ └── [[...mdxPath]] │ │ │ └── page.tsx │ │ ├── index-page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── icon.svg ├── public │ ├── favicon.ico │ └── configuring.svg ├── next-env.d.ts ├── next-sitemap.config.js ├── README.md ├── tsconfig.json ├── .eslintrc.cjs ├── tailwind.config.ts ├── next.config.ts └── package.json ├── tsconfig.build.json ├── src ├── helpers │ ├── index.ts │ ├── utils.ts │ ├── get-config.ts │ ├── find-config.ts │ └── cosmiconfig.ts ├── index.ts ├── extensions │ └── endpoints.ts ├── types.ts ├── extension.ts ├── errors.ts ├── loaders.ts ├── config.ts └── project-config.ts ├── .vscode └── settings.json ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug_report.md ├── workflows │ ├── release.yml │ ├── pr.yml │ ├── website.yml │ └── main.yml ├── renovate.json └── PULL_REQUEST_TEMPLATE.md ├── .prettierrc ├── .changeset ├── config.json └── README.md ├── tsconfig.json ├── vitest.config.ts ├── scripts └── postbuild.ts ├── .eslintrc.cjs ├── test ├── utils │ ├── runner.ts │ └── temp-dir.ts ├── loaders.spec.ts └── config.spec.ts ├── LICENSE ├── README.md ├── package.json ├── config-schema.json └── CHANGELOG.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | out/ 3 | .next/ 4 | .husky/_/ 5 | .bob/ 6 | pnpm-lock.yaml 7 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | export { default } from '@theguild/tailwind-config/postcss.config'; 2 | -------------------------------------------------------------------------------- /website/src/mdx-components.js: -------------------------------------------------------------------------------- 1 | export { useMDXComponents } from '@theguild/components/server'; 2 | -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-config/HEAD/website/public/favicon.ico -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["test"] 5 | } 6 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './find-config.js'; 2 | export * from './get-config.js'; 3 | export * from './utils.js'; 4 | -------------------------------------------------------------------------------- /website/src/content/user/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | usage: 'Usage', 3 | schema: 'Specifying Schema', 4 | documents: 'Specifying Documents', 5 | }; 6 | -------------------------------------------------------------------------------- /website/src/content/installation.mdx: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | GraphQL Config can be installed using your favorite package manager: 4 | 5 | ```sh npm2yarn 6 | npm i graphql-config 7 | ``` 8 | -------------------------------------------------------------------------------- /website/src/content/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: 'Introduction', 3 | installation: 'Installation', 4 | user: "I'm a User", 5 | library: "I'm a Library Author", 6 | migration: 'Migration', 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "coverage": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store/ 2 | .idea/ 3 | node_modules/ 4 | coverage/ 5 | dist/ 6 | out.txt 7 | package-lock.json 8 | .next/ 9 | *.log 10 | .bob 11 | 12 | website/public/_redirects 13 | website/public/sitemap.xml 14 | website/out/ 15 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /website/src/content/library/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'load-config': 'Loading Config', 3 | extensions: 'Extensions', 4 | loaders: 'Loaders', 5 | 'graphql-config': 'GraphQLConfig', 6 | 'graphql-project-config': 'GraphQLProjectConfig', 7 | }; 8 | -------------------------------------------------------------------------------- /website/next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | export default { 3 | siteUrl: process.env.SITE_URL || 'https://the-guild.dev/graphql/config', 4 | generateIndexSitemap: false, 5 | exclude: ['*/_meta'], 6 | output: 'export', 7 | }; 8 | -------------------------------------------------------------------------------- /website/src/app/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: { 3 | display: 'hidden', 4 | }, 5 | docs: { 6 | title: 'Documentation', 7 | type: 'page', 8 | }, 9 | changelog: { 10 | type: 'page', 11 | theme: { 12 | timestamp: false, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Have a question? 4 | url: https://github.com/kamilkisiela/graphql-config/discussions/new 5 | about: Not sure about something? need help from the community? have a question to our team? please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "overrides": [ 6 | { 7 | "files": "*.{md,mdx}", 8 | "options": { 9 | "semi": false, 10 | "trailingComma": "none", 11 | "arrowParens": "avoid" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /website/src/app/giscus.tsx: -------------------------------------------------------------------------------- 1 | import { Giscus as Giscus_ } from '@theguild/components'; 2 | 3 | export const Giscus = () => { 4 | return ( 5 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Source, Loader } from '@graphql-tools/utils'; 2 | export { GraphQLConfig, loadConfig, loadConfigSync } from './config.js'; 3 | export { GraphQLProjectConfig } from './project-config.js'; 4 | export { GraphQLExtensionDeclaration } from './extension.js'; 5 | export * from './types.js'; 6 | export * from './errors.js'; 7 | export { LoadersRegistry } from './loaders.js'; 8 | -------------------------------------------------------------------------------- /website/src/app/changelog/page.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | searchable: false 3 | --- 4 | 5 | import fs from 'node:fs/promises' 6 | import { compileMdx, MDXRemote } from '@theguild/components/server' 7 | 8 | # Changelog 9 | 10 | 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | stable: 10 | uses: the-guild-org/shared-config/.github/workflows/release-stable.yml@main 11 | with: 12 | releaseScript: release 13 | nodeVersion: 23 14 | packageManager: 'pnpm' 15 | secrets: 16 | githubToken: ${{ secrets.GITHUB_TOKEN }} 17 | npmToken: ${{ secrets.NPM_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>the-guild-org/shared-config:renovate"], 3 | "labels": ["dependencies"], 4 | "automerge": true, 5 | "major": { 6 | "automerge": false 7 | }, 8 | "prConcurrentLimit": 5, 9 | "prCreation": "immediate", 10 | "prHourlyLimit": 5, 11 | "timezone": "Europe/Warsaw", 12 | "schedule": ["after 10pm", "before 6:00am"], 13 | "vulnerabilityAlerts": { 14 | "assignees": ["@kamilkisiela"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.1.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "kamilkisiela/graphql-config" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [], 11 | "snapshot": { 12 | "useCalculatedVersion": true, 13 | "prereleaseTemplate": "{tag}-{datetime}-{commit}" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | ### Installation 4 | 5 | Run `pnpm install` in the root folder 6 | 7 | ### Local Development 8 | 9 | Run `pnpm start` in the website root 10 | 11 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 12 | 13 | ### Build 14 | 15 | Run `pnpm build` in the website root 16 | 17 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 18 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es2021", 5 | "lib": ["es2021"], 6 | "outDir": "dist", 7 | "skipLibCheck": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "resolveJsonModule": true, 16 | "types": ["vitest/globals"], 17 | "paths": { 18 | "graphql-config": ["./src/index.ts"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { defineConfig } from 'vitest/config'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | const CWD = process.cwd(); 6 | 7 | export default defineConfig({ 8 | test: { 9 | globals: true, 10 | alias: { 11 | 'graphql-config': path.join(CWD, 'src', 'index.ts'), 12 | // fixes Duplicate "graphql" modules cannot be used at the same time since different 13 | graphql: path.join(CWD, 'node_modules', 'graphql', 'index.js'), 14 | }, 15 | }, 16 | plugins: [tsconfigPaths()], 17 | }); 18 | -------------------------------------------------------------------------------- /website/src/content/user/documents.mdx: -------------------------------------------------------------------------------- 1 | # Specifying Documents 2 | 3 | GraphQL Config supports not only a schema but GraphQL Operations and Fragments too. 4 | 5 | ## Multiple Files 6 | 7 | You can specify a list of files: 8 | 9 | ```yaml 10 | documents: 11 | - ./documents/foo.graphql 12 | - ./documents/bar.graphql 13 | - ./documents/baz.graphql 14 | ``` 15 | 16 | Use a glob pattern to find and include operations and fragments: 17 | 18 | ```yaml 19 | documents: ./documents/*.graphql 20 | ``` 21 | 22 | GraphQL Config reads any matching files and parses them into `DocumentNode` objects. 23 | -------------------------------------------------------------------------------- /scripts/postbuild.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { writeFile, readFile } from 'node:fs/promises'; 3 | import path from 'node:path'; 4 | 5 | const filePath = path.resolve(process.cwd(), 'dist/esm/helpers/cosmiconfig.js'); 6 | 7 | console.time('done'); 8 | const content = await readFile(filePath, 'utf8'); 9 | 10 | await writeFile( 11 | filePath, 12 | ` 13 | import { createRequire } from 'module'; 14 | const require = createRequire(import.meta.url); 15 | import { fileURLToPath } from 'url'; 16 | const __filename = fileURLToPath(import.meta.url); 17 | ${content}`.trimStart(), 18 | ); 19 | console.timeEnd('done'); 20 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | reportUnusedDisableDirectives: true, 4 | env: { 5 | node: true, 6 | }, 7 | // TODO: remove this line to lint website folder from root folder 8 | ignorePatterns: 'website', 9 | overrides: [ 10 | { 11 | files: '*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,cjsx,ctsx,mjsx,mtsx}', 12 | parser: '@typescript-eslint/parser', 13 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 14 | rules: { 15 | 'no-console': 'error', 16 | '@typescript-eslint/no-explicit-any': 'warn', 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/extensions/endpoints.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLExtensionDeclaration } from '../extension.js'; 2 | import type { WithList } from '../types.js'; 3 | 4 | export interface Endpoint { 5 | url: string; 6 | headers?: Record>; 7 | introspect?: boolean; 8 | subscription?: { 9 | url: string; 10 | // TODO: remove undefined in v5 11 | connectionParams?: Record; 12 | }; 13 | } 14 | 15 | export type Endpoints = Record; 16 | 17 | export const EndpointsExtension: GraphQLExtensionDeclaration = () => { 18 | return { 19 | name: 'endpoints', 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "allowJs": true, 11 | "noEmit": true, 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the core of this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | 8 | 9 | 10 | **Describe the solution you'd like** 11 | 12 | 13 | 14 | **Describe alternatives you've considered** 15 | 16 | 17 | 18 | **Additional context** 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Canary Release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | # dependencies: 10 | # uses: the-guild-org/shared-config/.github/workflows/changesets-dependencies.yaml@main 11 | # with: 12 | # preCommit: 'pnpm run prettier' 13 | # installDependencies: true 14 | # packageManager: 'pnpm' 15 | # secrets: 16 | # githubToken: ${{ secrets.GUILD_BOT_TOKEN }} 17 | 18 | canary: 19 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 20 | with: 21 | npmTag: alpha 22 | buildScript: build 23 | nodeVersion: 23 24 | packageManager: 'pnpm' 25 | secrets: 26 | githubToken: ${{ secrets.GITHUB_TOKEN }} 27 | npmToken: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /src/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import type { IGraphQLConfig, IGraphQLProject, IGraphQLProjects, IGraphQLProjectLegacy } from '../types.js'; 2 | 3 | export function isMultipleProjectConfig(config: IGraphQLConfig): config is IGraphQLProjects { 4 | return typeof (config as IGraphQLProjects).projects === 'object'; 5 | } 6 | 7 | export function isSingleProjectConfig(config: IGraphQLConfig): config is IGraphQLProject { 8 | return (config as IGraphQLProject).schema !== undefined; 9 | } 10 | 11 | export function isLegacyProjectConfig(config: IGraphQLConfig): config is IGraphQLProjectLegacy { 12 | return ( 13 | (config as IGraphQLProjectLegacy).schemaPath !== undefined || 14 | (config as IGraphQLProjectLegacy).includes !== undefined || 15 | (config as IGraphQLProjectLegacy).excludes !== undefined 16 | ); 17 | } 18 | 19 | export type MiddlewareFn = (input: T) => T; 20 | 21 | export function useMiddleware(fns: Array>) { 22 | return (input: T) => { 23 | if (fns.length) { 24 | return fns.reduce((obj, cb) => cb(obj), input); 25 | } 26 | 27 | return input; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /website/src/app/docs/[[...mdxPath]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { NextPageProps } from '@theguild/components'; 2 | import { generateStaticParamsFor, importPage } from '@theguild/components/pages'; 3 | import { useMDXComponents } from '../../../mdx-components'; 4 | import { Giscus } from '../../giscus'; 5 | 6 | export const generateStaticParams = generateStaticParamsFor('mdxPath'); 7 | 8 | export async function generateMetadata(props: NextPageProps<'...mdxPath'>) { 9 | const params = await props.params; 10 | const { metadata } = await importPage(params.mdxPath); 11 | return metadata; 12 | } 13 | 14 | // eslint-disable-next-line react-hooks/rules-of-hooks 15 | const Wrapper = useMDXComponents().wrapper; 16 | 17 | export default async function Page(props: NextPageProps<'...mdxPath'>) { 18 | const params = await props.params; 19 | const result = await importPage(params.mdxPath); 20 | const { default: MDXContent, toc, metadata } = result; 21 | return ( 22 | }> 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /website/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | ignorePatterns: ['!.*', '.next'], 4 | reportUnusedDisableDirectives: true, 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'next', 'prettier'], 6 | settings: { 7 | next: { 8 | rootDir: './src', 9 | }, 10 | }, 11 | rules: { 12 | 'no-else-return': ['error', { allowElseIf: false }], 13 | 'react/jsx-curly-brace-presence': ['error', 'never'], 14 | 'react/jsx-filename-extension': ['error', { extensions: ['.tsx'] }], 15 | 'import/order': [ 16 | 'error', 17 | { 18 | groups: ['builtin', 'external', 'internal'], 19 | pathGroups: [ 20 | { 21 | pattern: './*.css', 22 | group: 'unknown', 23 | position: 'after', 24 | }, 25 | ], 26 | }, 27 | ], 28 | }, 29 | overrides: [ 30 | { 31 | files: ['.eslintrc.cjs', 'next.config.mjs'], 32 | env: { 33 | node: true, 34 | }, 35 | rules: { 36 | '@typescript-eslint/no-var-requires': 'off', 37 | }, 38 | }, 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /test/utils/runner.ts: -------------------------------------------------------------------------------- 1 | type PromiseOf any> = T extends (...args: any[]) => Promise ? R : ReturnType; 2 | 3 | export function runTests< 4 | TSync extends (...args: any[]) => TResult, 5 | TAsync extends (...args: any[]) => Promise, 6 | TResult = ReturnType, 7 | >({ sync: executeSync, async: executeAsync }: { sync: TSync; async: TAsync }) { 8 | return ( 9 | testRunner: ( 10 | executeFn: (...args: Parameters) => Promise>, 11 | mode: 'sync' | 'async', 12 | ) => void, 13 | ) => { 14 | // sync 15 | describe('sync', () => { 16 | testRunner((...args: Parameters) => { 17 | return new Promise>((resolve, reject) => { 18 | try { 19 | const result: any = executeSync(...args); 20 | resolve(result); 21 | } catch (error) { 22 | reject(error); 23 | } 24 | }); 25 | }, 'sync'); 26 | }); 27 | // async 28 | describe('async', () => { 29 | testRunner(executeAsync as any, 'async'); 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /website/src/content/library/loaders.mdx: -------------------------------------------------------------------------------- 1 | # Loaders 2 | 3 | ## Available Loaders 4 | 5 | - [Schema from GraphQL Endpoint](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/url) 6 | - [Schema from Introspection result](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/json-file) 7 | - [Schema and Documents from GraphQL files](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/graphql-file) 8 | - [Schema and Documents from JavaScript and TypeScript files](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/code-file) 9 | - [Schema from a file on GitHub](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/github) 10 | - [Schema from a file on a specific branch of git repository](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/git) 11 | 12 | ## Writing Loaders 13 | 14 | Take a look at some example loaders [here](https://github.com/ardatan/graphql-tools/blob/master/packages/loaders), we also recommend to explore the [GraphQL File Loader code](https://github.com/ardatan/graphql-tools/blob/master/packages/loaders/graphql-file/src/load-from-gql-file.ts) as you plan your loader. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | --- 5 | 6 | ### Issue workflow progress 7 | 8 | 9 | 10 | _Progress of the issue based on the [Contributor Workflow](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md#a-typical-contributor-workflow)_ 11 | 12 | - [ ] 1. The issue provides a reproduction available on GitHub, Stackblitz or CodeSandbox 13 | > Make sure to fork this template and run `pnpm run generate` in the terminal. 14 | > 15 | > Please make sure the Codegen and plugins version under `package.json` matches yours. 16 | - [ ] 2. A failing test has been provided 17 | - [ ] 3. A local solution has been provided 18 | - [ ] 4. A pull request is pending review 19 | 20 | --- 21 | 22 | **Describe the bug** 23 | 24 | 25 | 26 | **To Reproduce** 27 | Steps to reproduce the behavior: 28 | 29 | 30 | 31 | **Expected behavior** 32 | 33 | 34 | 35 | **Environment:** 36 | 37 | - OS: 38 | - GraphQL Config Version: 39 | - NodeJS: 40 | 41 | **Additional context** 42 | 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: website 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | deployment: 13 | runs-on: ubuntu-latest 14 | if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: the-guild-org/shared-config/setup@main 22 | name: setup env 23 | with: 24 | nodeVersion: 23 25 | packageManager: 'pnpm' 26 | workingDirectory: website 27 | 28 | - uses: the-guild-org/shared-config/website-cf@main 29 | name: build and deploy website 30 | env: 31 | NEXT_BASE_PATH: ${{ github.ref == 'refs/heads/master' && '/graphql/config' || '' }} 32 | SITE_URL: ${{ github.ref == 'refs/heads/master' && 'https://the-guild.dev/graphql/config' || '' }} 33 | with: 34 | cloudflareApiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 35 | cloudflareAccountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 36 | githubToken: ${{ secrets.GITHUB_TOKEN }} 37 | projectName: graphql-config 38 | prId: ${{ github.event.pull_request.number }} 39 | websiteDirectory: website 40 | buildScript: pnpm run build 41 | artifactDir: out 42 | -------------------------------------------------------------------------------- /website/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | import { fontFamily } from 'tailwindcss/defaultTheme'; 3 | import baseConfig from '@theguild/tailwind-config'; 4 | 5 | const config: Config = { 6 | ...baseConfig, 7 | theme: { 8 | ...baseConfig.theme, 9 | extend: { 10 | ...baseConfig.theme.extend, 11 | fontFamily: { 12 | sans: ['var(--font-sans, ui-sans-serif)', ...fontFamily.sans], 13 | }, 14 | colors: { 15 | ...baseConfig.theme.extend.colors, 16 | primary: baseConfig.theme.extend.colors['hive-yellow'], 17 | }, 18 | keyframes: { 19 | 'accordion-down': { 20 | from: { height: '0', opacity: '0' }, 21 | to: { height: 'var(--radix-accordion-content-height)', opacity: '1' }, 22 | }, 23 | 'accordion-up': { 24 | from: { 25 | height: 'var(--radix-accordion-content-height)', 26 | opacity: '1', 27 | }, 28 | to: { height: '0', opacity: '0' }, 29 | }, 30 | scroll: { 31 | to: { 32 | transform: 'translate(calc(-50% - .5rem))', 33 | }, 34 | }, 35 | }, 36 | animation: { 37 | 'accordion-down': 'accordion-down 0.5s ease', 38 | 'accordion-up': 'accordion-up 0.5s ease', 39 | scroll: 'scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite', 40 | }, 41 | }, 42 | }, 43 | }; 44 | 45 | export default config; 46 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | paths-ignore: [website/**] 7 | push: 8 | branches: [master] 9 | paths-ignore: [website/**] 10 | 11 | jobs: 12 | test: 13 | name: Node ${{matrix.node_version}} on ${{matrix.os}} 14 | runs-on: ${{matrix.os}} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | node_version: [18, 20, 22, 23] 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 21 | 22 | - name: Setup env 23 | uses: the-guild-org/shared-config/setup@main 24 | with: 25 | packageManager: 'pnpm' 26 | nodeVersion: ${{matrix.node_version}} 27 | 28 | - name: Test 29 | run: pnpm run test 30 | 31 | - name: Build 32 | run: pnpm run build 33 | 34 | - name: Integrity check 35 | run: npx bob check 36 | 37 | lint: 38 | name: Lint 39 | uses: the-guild-org/shared-config/.github/workflows/lint.yml@main 40 | with: 41 | script: pnpm run ci:lint 42 | packageManager: 'pnpm' 43 | secrets: 44 | githubToken: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | prettier: 47 | name: Lint source files 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 51 | - name: Setup env 52 | uses: the-guild-org/shared-config/setup@main 53 | with: 54 | packageManager: 'pnpm' 55 | nodeVersion: 23 56 | 57 | - name: Lint Prettier 58 | run: pnpm run prettier:check 59 | -------------------------------------------------------------------------------- /website/next.config.ts: -------------------------------------------------------------------------------- 1 | import { withGuildDocs } from '@theguild/components/next.config'; 2 | 3 | export default withGuildDocs({ 4 | redirects: async () => 5 | Object.entries({ 6 | '/legacy': '/docs/migration', 7 | '/docs/user/user-introduction': '/docs', 8 | '/docs/user/user-installation': '/docs/installation', 9 | '/docs/recipes/migration': '/docs/migration', 10 | '/docs/user/user-documents': 'docs/user/documents', 11 | '/docs/library/api-graphql-config': '/docs/library/graphql-config', 12 | '/docs/library/api-graphql-project-config': '/docs/library/graphql-project-config', 13 | '/docs/library/author-extensions': '/docs/library/extensions', 14 | '/docs/library/author-load-config': '/docs/library/load-config', 15 | '/docs/library/author-loaders': '/docs/library/loaders', 16 | '/usage': '/docs/user/usage', 17 | '/docs/user/user-usage': '/docs/user/usage', 18 | '/docs/user': '/docs/user/usage', 19 | '/docs/introduction': '/docs', 20 | '/extensions': '/docs/library/extensions', 21 | '/introduction': '/docs', 22 | '/schema': '/docs/user/schema', 23 | '/docs/schema': '/docs/user/schema', 24 | '/load-config': '/docs/library/load-config', 25 | '/docs/user/user-schema': '/docs/user/schema', 26 | '/documents': '/docs/user/documents', 27 | }).map(([from, to]) => ({ 28 | source: from, 29 | destination: to, 30 | permanent: true, 31 | })), 32 | output: 'export', 33 | env: { 34 | SITE_URL: 'https://the-guild.dev/graphql/config', 35 | }, 36 | nextraConfig: { 37 | contentDirBasePath: '/docs', 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /website/src/content/library/graphql-config.mdx: -------------------------------------------------------------------------------- 1 | # GraphQLConfig 2 | 3 | The `GraphQLConfig` object is instantiated by calling [`loadConfig`](./load-config). 4 | 5 | A basic usage: 6 | 7 | ```ts 8 | import { loadConfig } from 'graphql-config' 9 | 10 | async function main() { 11 | const config = await loadConfig({ ... }) // an instance of GraphQLConfig 12 | } 13 | ``` 14 | 15 | ## API 16 | 17 | ### `filepath` 18 | 19 | _type: `string`_ 20 | 21 | An exact path of a config file. 22 | 23 | ### `dirpath` 24 | 25 | _type: `string`_ 26 | 27 | A path of a directory where GraphQL Config was found. 28 | 29 | ### `extensions` 30 | 31 | _type: `GraphQLExtensionsRegistry`_ 32 | 33 | A registry of provided extensions. 34 | 35 | ### `projects` 36 | 37 | _type: `{ [projectName: string]: GraphQLProjectConfig }`_ 38 | 39 | A key-value object where the key is a project's name but the value contains [`GraphQLProjectConfig`](./graphql-project-config) object. 40 | 41 | ### `getProject()` 42 | 43 | _type: `getProject(name?: string): GraphQLProjectConfig | never`_ 44 | 45 | Accepts a single argument, which is a project's name and returns that project. When no name is provided, it resolves with a default project. 46 | 47 | ### `getDefault()` 48 | 49 | _type: `getDefault(): GraphQLProjectConfig | never`_ 50 | 51 | Returns a default project. 52 | 53 | ### `getProjectForFile()` 54 | 55 | _type: `getProjectForFile(filepath: string): GraphQLProjectConfig | never`_ 56 | 57 | Allows getting projects based on a file path. It might be a path to a GraphQL file that contains SDL or Operations and Fragments but also any file [included](/docs/user/usage#include-exclude) in the project. 58 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type PointerWithConfiguration = { [key: string]: T }; 2 | 3 | /** 4 | * Configuration of each used extension 5 | */ 6 | export interface IExtensions { 7 | [name: string]: any; 8 | } 9 | 10 | /** 11 | * Multiple named projects 12 | */ 13 | export interface IGraphQLProjects { 14 | projects: { 15 | [name: string]: IGraphQLProject | IGraphQLProjectLegacy; 16 | }; 17 | } 18 | 19 | /** 20 | * Structure of GraphQL Config 21 | */ 22 | export type IGraphQLConfig = IGraphQLProject | IGraphQLProjects | IGraphQLProjectLegacy; 23 | 24 | /** 25 | * Legacy structure of GraphQL Config v2 26 | */ 27 | export interface IGraphQLProjectLegacy { 28 | schemaPath: string; 29 | includes?: string[]; 30 | excludes?: string[]; 31 | extensions?: IExtensions; 32 | } 33 | 34 | export declare type WithList = T | T[]; 35 | export declare type ElementOf = TList extends Array ? TElement : never; 36 | export declare type SchemaPointer = WithList< 37 | | string 38 | | { 39 | [url: string]: { 40 | headers: { 41 | [name: string]: string; 42 | }; 43 | }; 44 | } 45 | >; 46 | export declare type SchemaPointerSingle = ElementOf; 47 | export declare type DocumentGlobPathPointer = string; 48 | export declare type DocumentPointer = WithList; 49 | 50 | /** 51 | * GraphQL Project 52 | */ 53 | export interface IGraphQLProject { 54 | schema: SchemaPointer; 55 | documents?: DocumentPointer; 56 | extensions?: IExtensions; 57 | include?: WithList; 58 | exclude?: WithList; 59 | } 60 | 61 | export interface GraphQLConfigResult { 62 | config: IGraphQLConfig; 63 | filepath: string; 64 | } 65 | -------------------------------------------------------------------------------- /src/helpers/get-config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNotFoundError, ConfigEmptyError, composeMessage } from '../errors.js'; 2 | import { GraphQLConfigResult } from '../types.js'; 3 | import { ConfigSearchResult, createCosmiConfigSync, createCosmiConfig } from './cosmiconfig.js'; 4 | 5 | export async function getConfig({ 6 | filepath, 7 | configName, 8 | legacy = true, 9 | }: { 10 | filepath: string; 11 | configName: string; 12 | legacy?: boolean; 13 | }): Promise { 14 | validate(filepath); 15 | 16 | return resolve({ 17 | result: await createCosmiConfig(configName, legacy).load(filepath), 18 | filepath, 19 | }); 20 | } 21 | 22 | export function getConfigSync({ 23 | filepath, 24 | configName, 25 | legacy = true, 26 | }: { 27 | filepath: string; 28 | configName: string; 29 | legacy?: boolean; 30 | }): GraphQLConfigResult { 31 | validate(filepath); 32 | 33 | return resolve({ 34 | result: createCosmiConfigSync(configName, legacy).load(filepath), 35 | filepath, 36 | }); 37 | } 38 | 39 | // 40 | 41 | function resolve({ result, filepath }: { result?: ConfigSearchResult; filepath: string }) { 42 | if (!result) { 43 | throw new ConfigNotFoundError( 44 | composeMessage(`GraphQL Config file is not available: ${filepath}`, `Please check the config filepath.`), 45 | ); 46 | } 47 | 48 | if (result.isEmpty) { 49 | throw new ConfigEmptyError(composeMessage(`GraphQL Config file is empty.`, `Please check ${result.filepath}`)); 50 | } 51 | 52 | return { 53 | config: result.config as any, 54 | filepath: result.filepath, 55 | }; 56 | } 57 | 58 | function validate(filepath: string): void { 59 | if (!filepath) { 60 | throw new Error(`Defining a file path is required`); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-config/website", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "lint": "eslint --ignore-path ../.gitignore --ext js,jsx,cjs,mjs,ts,tsx,cts,mts .", 8 | "dev": "next", 9 | "build": "next build", 10 | "pagefind": "pagefind --site .next/server/app --output-path out/_pagefind", 11 | "postbuild": "next-sitemap && pnpm pagefind", 12 | "analyze": "cross-env ANALYZE=true pnpm run build" 13 | }, 14 | "dependencies": { 15 | "@theguild/components": "9.8.0", 16 | "next": "^15.3.3", 17 | "next-sitemap": "^4.2.3", 18 | "react": "^19.0.0", 19 | "react-dom": "^19.0.0" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | }, 33 | "devDependencies": { 34 | "@theguild/tailwind-config": "0.6.3", 35 | "@types/node": "22.14.0", 36 | "@types/react": "19.1.8", 37 | "@typescript-eslint/eslint-plugin": "8.38.0", 38 | "@typescript-eslint/parser": "8.38.0", 39 | "cross-env": "7.0.3", 40 | "eslint": "9.24.0", 41 | "eslint-config-next": "15.5.7", 42 | "eslint-config-prettier": "10.1.8", 43 | "pagefind": "1.3.0", 44 | "postcss-import": "16.1.1", 45 | "postcss-lightningcss": "1.0.1", 46 | "tailwindcss": "3.4.17", 47 | "typescript": "5.8.3" 48 | }, 49 | "packageManager": "pnpm@10.13.1", 50 | "engines": { 51 | "node": ">= 16.0.0", 52 | "pnpm": "^10.0.0" 53 | }, 54 | "pnpm": { 55 | "onlyBuiltDependencies": [ 56 | "sharp" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /website/src/content/library/load-config.mdx: -------------------------------------------------------------------------------- 1 | # Loading Config 2 | 3 | ## Load Config 4 | 5 | This function is the starting point for using GraphQL Config. It looks for a config file in [predefined search places](/docs/user/usage#config-search-places) in the current working directory. 6 | 7 | ### A Basic Usage Example (async): 8 | 9 | ```ts 10 | import { loadConfig } from 'graphql-config' 11 | 12 | async function main() { 13 | const config = await loadConfig({ ... }) // an instance of GraphQLConfig 14 | } 15 | ``` 16 | 17 | ### Synchronous Version: 18 | 19 | ```ts 20 | import { loadConfigSync } from 'graphql-config' 21 | 22 | function main() { 23 | const config = loadConfigSync({ 24 | /* ... */ 25 | }) // an instance of GraphQLConfig 26 | } 27 | ``` 28 | 29 | ## Options 30 | 31 | ### `filepath` 32 | 33 | _type: `string`_ 34 | 35 | An exact path of a config file. 36 | 37 | ### `rootDir` 38 | 39 | _type: `string`_ 40 | 41 | A path of a directory where GraphQL Config should look for a file _(uses process.cwd() by default)_. 42 | 43 | ### `configName` 44 | 45 | _type: `string`_ 46 | 47 | The name of the config file. It's `graphql` by default. Using `relay` as a config name instructs GraphQL Config to look for all the variations of possible config file names where one of them is `relay.config.js`. 48 | 49 | ### `extensions` 50 | 51 | _type: `GraphQLExtensionDeclaration[]`_ 52 | 53 | An array of `GraphQLExtensionDeclaration` objects, is placed to register extensions. 54 | 55 | ### `throwOnMissing` 56 | 57 | _type: `boolean`_ 58 | 59 | GraphQL Config throws an error where there's no config file by default. 60 | 61 | ### `throwOnEmpty` 62 | 63 | _type: `boolean`_ 64 | 65 | GraphQL Config by default throws an error if there's a config file but the file is empty. 66 | -------------------------------------------------------------------------------- /src/helpers/find-config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNotFoundError, ConfigEmptyError, composeMessage } from '../errors.js'; 2 | import { GraphQLConfigResult } from '../types.js'; 3 | import { createCosmiConfig, createCosmiConfigSync, ConfigSearchResult } from './cosmiconfig.js'; 4 | 5 | const CWD = process.cwd(); 6 | 7 | type FindConfigOptions = { 8 | rootDir: string; 9 | configName: string; 10 | legacy?: boolean; 11 | }; 12 | 13 | export async function findConfig({ 14 | rootDir = CWD, 15 | legacy = true, 16 | configName, 17 | }: FindConfigOptions): Promise { 18 | validate(rootDir); 19 | 20 | return resolve({ 21 | rootDir, 22 | result: await createCosmiConfig(configName, legacy).search(rootDir), 23 | }); 24 | } 25 | 26 | export function findConfigSync({ rootDir = CWD, legacy = true, configName }: FindConfigOptions): GraphQLConfigResult { 27 | validate(rootDir); 28 | 29 | return resolve({ 30 | rootDir, 31 | result: createCosmiConfigSync(configName, legacy).search(rootDir), 32 | }); 33 | } 34 | 35 | function validate(rootDir: string): void { 36 | if (!rootDir) { 37 | throw new Error(`Defining a root directory is required`); 38 | } 39 | } 40 | 41 | function resolve({ result, rootDir }: { result?: ConfigSearchResult; rootDir: string }) { 42 | if (!result) { 43 | throw new ConfigNotFoundError( 44 | composeMessage( 45 | `GraphQL Config file is not available in the provided config directory: ${rootDir}`, 46 | `Please check the config directory.`, 47 | ), 48 | ); 49 | } 50 | 51 | if (result.isEmpty) { 52 | throw new ConfigEmptyError(composeMessage(`GraphQL Config file is empty.`, `Please check ${result.filepath}`)); 53 | } 54 | 55 | return { 56 | config: result.config as any, 57 | filepath: result.filepath, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /website/src/content/migration.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from '@theguild/components' 2 | 3 | # Migration 4 | 5 | Here we explain a new syntax and how to migrate from `v2.0` to `v3.0`. 6 | 7 | 8 | **LIBRARY AUTHORS.** Legacy syntax (v2.0) is deprecated but still supported through the API of GraphQL Config v3.0. As 9 | a library author, you consume GraphQL Config API as usual, the legacy syntax is backported to a new one under the 10 | hood. 11 | 12 | 13 | ## Migration Tool 14 | 15 | We prepared a command-line tool to help you migrate your legacy GraphQL Config: 16 | 17 | ```sh 18 | npx graphql-config-migrate 19 | ``` 20 | 21 | The migration tool will ask a few questions and move your config files to the new syntax. 22 | 23 | ## New Syntax 24 | 25 | ### `schemaPath` 26 | 27 | Read ["Usage"](/docs/user/usage#schema) chapter to understand the `schema` field. 28 | 29 | ```diff 30 | - schemaPath: "schema.graphql" 31 | + schema: "schema.graphql" 32 | ``` 33 | 34 | ### `includes` and `excludes` 35 | 36 | Minor change here, remove the `s` at the end. The logic behind `include` and `exclude` stays the same but please read about it in ["Usage"](/docs/user/usage#include--exclude) chapter. 37 | 38 | ```diff 39 | - includes: "src/*.graphql" 40 | + include: "src/*.graphql" 41 | ``` 42 | 43 | ```diff 44 | - excludes: "tests/*.graphql" 45 | + exclude: "tests/*.graphql" 46 | ``` 47 | 48 | ### `documents` (new) 49 | 50 | Read ["Usage"](/docs/user/usage#documents) chapter to understand new `documents` field. 51 | 52 | ```diff 53 | + documents: "ui/*.graphql" 54 | ``` 55 | 56 | ### `extensions` 57 | 58 | No changes here, but please read the ["Usage"](/docs/user/usage#extensions) chapter. 59 | 60 | ### `projects` 61 | 62 | No changes here, but please read the ["Usage"](/docs/user/usage#projects) chapter. 63 | -------------------------------------------------------------------------------- /website/src/app/index-page.tsx: -------------------------------------------------------------------------------- 1 | import { FeatureList, HeroGradient, NPMBadge } from '@theguild/components'; 2 | import { ReactElement } from 'react'; 3 | import React from 'react'; 4 | 5 | export function IndexPage(): ReactElement { 6 | return ( 7 | <> 8 | 12 | One configuration for all your GraphQL tools. 13 |
14 | The easiest way to configure your development environment with your GraphQL Schema. 15 | 16 | } 17 | link={{ 18 | href: '/docs', 19 | children: 'Get Started', 20 | title: 'Get started with GraphQL Config', 21 | }} 22 | version={} 23 | colors={['#5f6184', '#000']} 24 | /> 25 | 26 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; 2 | import { UrlLoader } from '@graphql-tools/url-loader'; 3 | import { JsonFileLoader } from '@graphql-tools/json-file-loader'; 4 | import { LoadersRegistry } from './loaders.js'; 5 | 6 | export type GraphQLExtensionDeclaration = (api: ExtensionAPI) => GraphQLConfigExtension; 7 | 8 | export interface ExtensionAPI { 9 | logger: any; 10 | loaders: { 11 | schema: Pick; 12 | documents: Pick; 13 | }; 14 | } 15 | 16 | export interface GraphQLConfigExtension { 17 | name: string; 18 | } 19 | 20 | export class GraphQLExtensionsRegistry { 21 | private readonly _extensions: { 22 | [name: string]: GraphQLConfigExtension; 23 | } = {}; 24 | 25 | readonly loaders: { 26 | schema: LoadersRegistry; 27 | documents: LoadersRegistry; 28 | }; 29 | 30 | constructor({ cwd }: { cwd: string }) { 31 | this.loaders = { 32 | schema: new LoadersRegistry({ cwd }), 33 | documents: new LoadersRegistry({ cwd }), 34 | }; 35 | 36 | // schema 37 | this.loaders.schema.register(new GraphQLFileLoader()); 38 | this.loaders.schema.register(new UrlLoader()); 39 | this.loaders.schema.register(new JsonFileLoader()); 40 | // documents 41 | this.loaders.documents.register(new GraphQLFileLoader()); 42 | } 43 | 44 | register(extensionFn: GraphQLExtensionDeclaration): void { 45 | const extension = extensionFn({ 46 | logger: {}, 47 | loaders: this.loaders, 48 | }); 49 | this._extensions[extension.name] = extension; 50 | } 51 | 52 | has(extensionName: string) { 53 | return !!this._extensions[extensionName]; 54 | } 55 | 56 | get(extensionName: string) { 57 | return this._extensions[extensionName]; 58 | } 59 | 60 | names(): string[] { 61 | return Object.keys(this._extensions); 62 | } 63 | 64 | forEach(cb: (extension: GraphQLConfigExtension) => void) { 65 | for (const extensionName in this._extensions) { 66 | cb(this._extensions[extensionName]); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- TODO: fix lint error 2 | function ExtendableBuiltin(cls: T): T { 3 | function ExtendableBuiltin(this: any, ...args) { 4 | cls.apply(this, args); 5 | } 6 | ExtendableBuiltin.prototype = Object.create(cls.prototype); 7 | Object.setPrototypeOf(ExtendableBuiltin, cls); 8 | 9 | return ExtendableBuiltin as any; 10 | } 11 | 12 | export function composeMessage(...lines: string[]): string { 13 | return lines.join('\n'); 14 | } 15 | 16 | export class ConfigNotFoundError extends ExtendableBuiltin(Error) { 17 | constructor(message: string) { 18 | super(message); 19 | this.name = this.constructor.name; 20 | this.message = message; 21 | } 22 | } 23 | 24 | export class ConfigEmptyError extends ExtendableBuiltin(Error) { 25 | constructor(message: string) { 26 | super(message); 27 | this.name = this.constructor.name; 28 | this.message = message; 29 | } 30 | } 31 | // TODO: remove in v5 32 | export class ConfigInvalidError extends ExtendableBuiltin(Error) { 33 | constructor(message: string) { 34 | super(message); 35 | this.name = this.constructor.name; 36 | this.message = message; 37 | } 38 | } 39 | 40 | export class ProjectNotFoundError extends ExtendableBuiltin(Error) { 41 | constructor(message: string) { 42 | super(message); 43 | this.name = this.constructor.name; 44 | this.message = message; 45 | } 46 | } 47 | // TODO: remove in v5 48 | export class LoadersMissingError extends ExtendableBuiltin(Error) { 49 | constructor(message: string) { 50 | super(message); 51 | this.name = this.constructor.name; 52 | this.message = message; 53 | } 54 | } 55 | // TODO: remove in v5 56 | export class LoaderNoResultError extends ExtendableBuiltin(Error) { 57 | constructor(message: string) { 58 | super(message); 59 | this.name = this.constructor.name; 60 | this.message = message; 61 | } 62 | } 63 | 64 | export class ExtensionMissingError extends ExtendableBuiltin(Error) { 65 | constructor(message: string) { 66 | super(message); 67 | this.name = this.constructor.name; 68 | this.message = message; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 🚨 **IMPORTANT: Please do not create a Pull Request without creating an issue first.** 2 | 3 | _Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request._ 4 | 5 | ## Description 6 | 7 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 8 | 9 | Fixes # (issue) 10 | 11 | ## Type of change 12 | 13 | Please delete options that are not relevant. 14 | 15 | - [ ] Bug fix (non-breaking change which fixes an issue) 16 | - [ ] New feature (non-breaking change which adds functionality) 17 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 18 | - [ ] This change requires a documentation update 19 | 20 | ## Screenshots/Sandbox (if appropriate/relevant): 21 | 22 | Adding links to sandbox or providing screenshots can help us understand more about this PR and take action on it as appropriate 23 | 24 | ## How Has This Been Tested? 25 | 26 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 27 | 28 | - [ ] Test A 29 | - [ ] Test B 30 | 31 | **Test Environment**: 32 | 33 | - OS: 34 | - GraphQL Config Version: 35 | - NodeJS: 36 | 37 | ## Checklist: 38 | 39 | - [ ] I have followed the [CONTRIBUTING](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md) doc and the style guidelines of this project 40 | - [ ] I have performed a self-review of my own code 41 | - [ ] I have commented my code, particularly in hard-to-understand areas 42 | - [ ] I have made corresponding changes to the documentation 43 | - [ ] My changes generate no new warnings 44 | - [ ] I have added tests that prove my fix is effective or that my feature works 45 | - [ ] New and existing unit tests and linter rules pass locally with my changes 46 | - [ ] Any dependent changes have been merged and published in downstream modules 47 | 48 | ## Further comments 49 | 50 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 51 | -------------------------------------------------------------------------------- /website/src/content/user/schema.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from '@theguild/components' 2 | 3 | # Specifying Schema 4 | 5 | The simplest config specifies only `schema` which points to the source of GraphQL schema. 6 | 7 | ```yaml 8 | schema: ./schema.graphql 9 | ``` 10 | 11 | GraphQL Config can start with a single schema and grow from there. 12 | 13 | ## Multiple Files 14 | 15 | GraphQL Config can also assemble multiple modularized schemas into a single GraphQL schema object. 16 | 17 | You can specify a list of files: 18 | 19 | ```yaml 20 | schema: 21 | - ./foo.graphql 22 | - ./bar.graphql 23 | - ./baz.graphql 24 | ``` 25 | 26 | Alternatively, you can use a glob pattern to find and include pieces of schema: 27 | 28 | ```yaml 29 | schema: ./*.graphql 30 | ``` 31 | 32 | GraphQL Config looks for those files, reads the files and merges them to produce a GraphQL schema object. 33 | 34 | ## Introspection Result 35 | 36 | A very common way to describe a GraphQL schema is to run an introspection query on it and save the resulting output as a JSON file. GraphQL Config can also read these files into schema objects. 37 | 38 | ```yaml 39 | schema: ./schema.json 40 | ``` 41 | 42 | Note that JSON introspection results are parsed for both file validity and for schema validity; if either check fails, an error message will be passed back to the caller. 43 | 44 | ## Endpoint 45 | 46 | In case you want to access a running GraphQL server via its endpoint, you can pass its address into the configuration file. 47 | 48 | ```yaml 49 | schema: http://localhost:4000/graphql 50 | ``` 51 | 52 | ## Environment Variables 53 | 54 | It is possible to load definitions from environment variables, with or without fallback values. 55 | 56 | ```yaml 57 | schema: ${SCHEMA_FILE:./schema.json} 58 | ``` 59 | 60 | If you want to define a fallback endpoint you may wrap your value with quotation marks. 61 | 62 | ```yaml 63 | schema: ${SCHEMA_ENDPOINT:"http://localhost:4000/graphql"} 64 | ``` 65 | 66 | ## Passing Headers 67 | 68 | If you need to pass headers in the schema request you can do it this way: 69 | 70 | ```yaml 71 | schema: 72 | - http://localhost:4000/graphql: 73 | headers: 74 | Authorization: Token 75 | ``` 76 | 77 | Pay special attention to the indentation of the headers block. 78 | -------------------------------------------------------------------------------- /website/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from 'react'; 2 | import { ConfigLogo, GitHubIcon, HiveFooter, PaperIcon, PencilIcon, PRODUCTS } from '@theguild/components'; 3 | import { getDefaultMetadata, getPageMap, GuildLayout } from '@theguild/components/server'; 4 | import '@theguild/components/style.css'; 5 | 6 | const description = PRODUCTS.CONFIG.title; 7 | const websiteName = 'GraphQL-Config'; 8 | 9 | export const metadata = getDefaultMetadata({ 10 | description, 11 | websiteName, 12 | productName: 'CONFIG', 13 | }); 14 | 15 | const RootLayout: FC<{ 16 | children: ReactNode; 17 | }> = async ({ children }) => { 18 | const logo = ; 19 | return ( 20 | .light_#h-navmenu-container]:max-w-[1392px]', 24 | }} 25 | websiteName={websiteName} 26 | description={description} 27 | logo={logo} 28 | layoutProps={{ 29 | docsRepositoryBase: 'https://github.com/kamilkisiela/graphql-config/tree/master/website', 30 | footer: ( 31 | 34 | {logo} 35 | {websiteName} 36 | 37 | } 38 | description={description} 39 | items={{ 40 | resources: [ 41 | { 42 | children: 'Changelog', 43 | href: '/changelog', 44 | title: 'Changelog', 45 | }, 46 | ], 47 | }} 48 | /> 49 | ), 50 | }} 51 | pageMap={await getPageMap()} 52 | navbarProps={{ 53 | developerMenu: [ 54 | { 55 | href: '/docs', 56 | icon: , 57 | children: 'Documentation', 58 | }, 59 | { 60 | href: 'https://the-guild.dev/blog', 61 | icon: , 62 | children: 'Blog', 63 | }, 64 | { 65 | href: 'https://github.com/kamilkisiela/graphql-config', 66 | icon: , 67 | children: 'GitHub', 68 | }, 69 | ], 70 | }} 71 | lightOnlyPages={['/']} 72 | > 73 | {children} 74 | 75 | ); 76 | }; 77 | 78 | export default RootLayout; 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GraphQLConf 2024 Banner: September 10-12, San Francisco. Hosted by the GraphQL Foundation](https://github.com/user-attachments/assets/bdb8cd5d-5186-4ece-b06b-b00a499b7868)](https://graphql.org/conf/2024/?utm_source=github&utm_medium=graphql_config&utm_campaign=readme) 2 | 3 | 4 | 5 | 6 | [![npm version](https://badge.fury.io/js/graphql-config.svg)](https://npmjs.com/package/graphql-config) 7 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 8 | [![renovate-app badge](https://img.shields.io/badge/renovate-app-blue.svg)](https://renovateapp.com/) 9 | [![Discord Chat](https://img.shields.io/discord/625400653321076807)](https://discord.gg/xud7bH9) 10 | 11 | **GraphQL Config** - one configuration for all your GraphQL tools (supported by most tools, editors & IDEs). 12 | The easiest way to configure your development environment with your GraphQL Schema. 13 | 14 | **As a developer**, you gain simplicity and a central place to setup libraries, tools and your IDE extensions. 15 | 16 | **As a library author** GraphQL Config makes it easier to maintain the code responsible for handling configuration, loading GraphQL schemas or even files with GraphQL operations and fragments. GraphQL Config provides a set of useful methods and an easy-to-work-with API. 17 | 18 | ## Example 19 | 20 | ```yaml 21 | schema: ./schema.json 22 | documents: ./src/components/**/*.jsx 23 | ``` 24 | 25 | ## Installation and Usage 26 | 27 | Visit our website [**graphql-config.com**](https://graphql-config.com/) to learn more about the GraphQL Config. 28 | 29 | ## Help & Community 30 | 31 | Join our [Discord chat](https://discord.gg/xud7bH9) if you run into issues or have questions. We love talking to you! 32 | 33 | ## Contributions 34 | 35 | Contributions, issues and feature requests are very welcome. If you are using this package and fixed a bug for yourself, please consider submitting a PR! 36 | 37 | And if this is your first time contributing to this project, please do read our [Contributor Workflow Guide](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md) before you get started off. 38 | 39 | ### Code of Conduct 40 | 41 | Help us keep GraphQL Config open and inclusive. Please read and follow our [Code of Conduct](https://github.com/the-guild-org/Stack/blob/master/CODE_OF_CONDUCT.md) as adopted from [Contributor Covenant](https://www.contributor-covenant.org/) 42 | 43 | ### License 44 | 45 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg?maxAge=2592000)](https://raw.githubusercontent.com/apollostack/apollo-ios/master/LICENSE) 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /website/src/content/index.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from '@theguild/components' 2 | 3 | # Introduction 4 | 5 | There are many ways to configure your application to use GraphQL, and while it is often enough to specify configuration options directly in your application code, maintaining and understanding the hard-coded configuration options may become a challenge as the scale grows. We recommend configuring your application with a `.graphqlrc` file that contains commonly needed GraphQL-related artifacts. 6 | 7 | The configuration should be placed on the root folder if you are using workspaces. 8 | 9 | Think about GraphQL Config as **one configuration for all your GraphQL tools**. 10 | 11 | The basic idea is to have one configuration file that any GraphQL tool could consume. 12 | 13 | ## As a Developer 14 | 15 | From the developer perspective, you gain simplicity and a central place to setup libraries, tools and your IDE extensions. 16 | 17 | ## As a Library Author 18 | 19 | From the point of view of a library author, GraphQL Config makes it easier to maintain the code responsible for handling configuration, loading GraphQL schemas or even files with GraphQL operations and fragments. GraphQL Config provides a set of useful methods and an easy-to-work-with API. 20 | 21 | ## Examples 22 | 23 | Learn more in [usage docs](./usage). 24 | 25 | ### `.graphqlrc` or `.graphqlrc.yml/yaml` 26 | 27 | ```yaml 28 | schema: 'packages/api/src/schema.graphql' 29 | documents: 'packages/app/src/components/**/*.graphql' 30 | extensions: 31 | customExtension: 32 | foo: true 33 | ``` 34 | 35 | ### `.graphqlrc`, `graphql.config.json` or `.graphqlrc.json` 36 | 37 | ```json 38 | { 39 | "schema": "https://localhost:8000" 40 | } 41 | ``` 42 | 43 | ### `graphql.config.toml` or `.graphqlrc.toml` 44 | 45 | ```toml 46 | schema = "https://localhost:8080" 47 | ``` 48 | 49 | **Note**: This config requires `cosmiconfig-toml-loader` to be installed. 50 | 51 | ### `graphql.config.js` or `.graphqlrc.js` 52 | 53 | ```js 54 | module.exports = { 55 | schema: 'https://localhost:8000' 56 | } 57 | ``` 58 | 59 | ### `graphql.config.ts` or `.graphqlrc.ts` 60 | 61 | ```ts 62 | import type { IGraphQLConfig } from 'graphql-config' 63 | 64 | const config: IGraphQLConfig = { 65 | schema: 'https://localhost:8000' 66 | } 67 | 68 | export default config 69 | ``` 70 | 71 | ### Custom Paths 72 | 73 | Custom extension paths with `.mycustomrc.js`, `mycustom.config.yml`, etc. - any filename listed in [usage docs](usage) with `graphql` replaced by the `loadConfig(){:ts}` parameter [`configName`](load-config#configname). 74 | 75 | ```js 76 | await loadConfig({ configName: 'mycustom' }) 77 | ``` 78 | 79 | would allow you to use `.mycustomrc.js`: 80 | 81 | ```js 82 | module.exports = { 83 | schema: 'https://localhost:8000' 84 | } 85 | ``` 86 | 87 | ### Lookup Order 88 | 89 | We are using `cosmiconfig` to load the schema, and it uses the following flow to look for configurations: 90 | 91 | 1. a `package.json` property. 92 | 1. a JSON or YAML, extensionless "rc file". 93 | 1. a "rc file" with the extensions `.json`, `.yaml`, `.yml`, `.toml`, `.ts` or `.js`. 94 | 1. a `.config.js` CommonJS module, or a `.config.ts` file. 95 | -------------------------------------------------------------------------------- /test/utils/temp-dir.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { deleteSync } from 'del'; 3 | import { makeDirectorySync } from 'make-dir'; 4 | import parentModule from 'parent-module'; 5 | import os from 'node:os'; 6 | import type { Mock, MockInstance } from 'vitest'; 7 | import fs from 'node:fs'; 8 | 9 | function normalizeDirectorySlash(pathname: string): string { 10 | const normalizeCrossPlatform = pathname.replace(/\\/g, '/'); 11 | 12 | return normalizeCrossPlatform; 13 | } 14 | 15 | export class TempDir { 16 | dir: string; 17 | 18 | constructor() { 19 | /** 20 | * Get the actual path for temp directories that are symlinks (macOS). 21 | * Without the actual path, tests that use process.chdir will unexpectedly 22 | * return the real path instead of symlink path. 23 | */ 24 | const tempDir = fs.realpathSync(os.tmpdir()); 25 | 26 | /** 27 | * Get the pathname of the file that imported util.js. 28 | * Used to create a unique directory name for each test suite. 29 | */ 30 | const parent = parentModule() || 'cosmiconfig'; 31 | const relativeParent = path.relative(process.cwd(), parent); 32 | 33 | /** 34 | * Each temp directory will be unique to the test file. 35 | * This ensures that temp files/dirs won't cause side effects for other tests. 36 | */ 37 | this.dir = path.resolve(tempDir, 'cosmiconfig', `${relativeParent}-dir`); 38 | 39 | // create directory 40 | makeDirectorySync(this.dir); 41 | } 42 | 43 | absolutePath(dir: string): string { 44 | // Use path.join to ensure dir is always inside the working temp directory 45 | const absolutePath = path.join(this.dir, dir); 46 | 47 | return absolutePath; 48 | } 49 | 50 | createDir(dir: string): void { 51 | const dirname = this.absolutePath(dir); 52 | makeDirectorySync(dirname); 53 | } 54 | 55 | createFile(file: string, contents: string): void { 56 | const filePath = this.absolutePath(file); 57 | const fileDir = path.parse(filePath).dir; 58 | makeDirectorySync(fileDir); 59 | 60 | fs.writeFileSync(filePath, `${contents}\n`); 61 | } 62 | 63 | getSpyPathCalls(spy: Mock | MockInstance): string[] { 64 | const calls = spy.mock.calls; 65 | 66 | const result = calls.map((call): string => { 67 | const [filePath] = call; 68 | const relativePath = path.relative(this.dir, filePath); 69 | 70 | /** 71 | * Replace Windows backslash directory separators with forward slashes 72 | * so expected paths will be consistent cross-platform 73 | */ 74 | const normalizeCrossPlatform = normalizeDirectorySlash(relativePath); 75 | 76 | return normalizeCrossPlatform; 77 | }); 78 | 79 | return result; 80 | } 81 | 82 | clean(): string[] { 83 | const cleanPattern = normalizeDirectorySlash(this.absolutePath('**/*')); 84 | const removed = deleteSync(cleanPattern, { 85 | force: true, 86 | dot: true, 87 | }); 88 | 89 | return removed; 90 | } 91 | 92 | deleteTempDir(): string[] { 93 | const removed = deleteSync(normalizeDirectorySlash(this.dir), { 94 | force: true, 95 | dot: true, 96 | }); 97 | 98 | return removed; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /website/src/content/library/graphql-project-config.mdx: -------------------------------------------------------------------------------- 1 | # GraphQLProjectConfig 2 | 3 | The `GraphQLProjectConfig` represents projects defined in the GraphQL Config file. 4 | 5 | A basic usage: 6 | 7 | ```ts 8 | import { loadConfig } from 'graphql-config' 9 | 10 | async function main() { 11 | const config = await loadConfig({ ... }) // an instance of GraphQLConfig 12 | 13 | const project = config.getDefault() // an instance of GraphQLProjectConfig 14 | } 15 | ``` 16 | 17 | ## API 18 | 19 | ### `name` 20 | 21 | _type: `string`_ 22 | 23 | Project's name. 24 | 25 | ### `filepath` 26 | 27 | _type: `string`_ 28 | 29 | An exact path of a config file. 30 | 31 | ### `dirpath` 32 | 33 | _type: `string`_ 34 | 35 | A path of a directory where GraphQL Config was found. 36 | 37 | ### `extensions` 38 | 39 | _type: `IExtensions`_ 40 | 41 | A raw key-value object representing extensions. 42 | 43 | ### `schema` 44 | 45 | _type: `SchemaPointer`_ 46 | 47 | Value defined in `schema` property, in the config file. 48 | 49 | ### `documents` 50 | 51 | _type: `DocumentPointer`_ 52 | 53 | Value defined in `documents` property, in the config file. 54 | 55 | ### `include` 56 | 57 | _type: `string | string[]`_ 58 | 59 | Value defined in `include` property, in the config file. 60 | 61 | ### `exclude` 62 | 63 | _type: `string | string[]`_ 64 | 65 | Value defined in `exclude` property, in the config file. 66 | 67 | ### `projects` 68 | 69 | _type: `{ [projectName: string]: GraphQLProjectConfig }`_ 70 | 71 | A key-value object where the key is a project's name but the value contains `GraphQLProjectConfig` object. 72 | 73 | ### `hasExtension()` 74 | 75 | _type: `hasExtension(name: string): boolean`_ 76 | 77 | Checks if the project contains the extension. 78 | 79 | ### `getDefault()` 80 | 81 | _type: `getDefault(): GraphQLProjectConfig | never`_ 82 | 83 | Returns a default project. 84 | 85 | ### `extension()` 86 | 87 | _type: `extension(name: string): T`_ 88 | 89 | Allows accessing extension's configuration + `schema`, `documents`, `include` and `exclude` values are also added to the object. 90 | 91 | ### `getSchema()` 92 | 93 | _type: `getSchema(): Promise`_ 94 | 95 | _type: `getSchema(out: 'DocumentNode'): Promise`_ 96 | 97 | _type: `getSchema(out: 'GraphQLSchema'): Promise`_ 98 | 99 | Allows accessing `GraphQLSchema` object based on provided information (in `schema` property of project's configuration). 100 | 101 | ### `getSchemaSync()` 102 | 103 | _type: `getSchemaSync(): GraphQLSchema`_ 104 | 105 | _type: `getSchemaSync(out: 'DocumentNode'): DocumentNode`_ 106 | 107 | _type: `getSchemaSync(out: 'GraphQLSchema'): GraphQLSchema`_ 108 | 109 | Allows accessing `GraphQLSchema` object based on provided information (in `schema` property of project's configuration). 110 | 111 | ### `getDocuments()` 112 | 113 | _type: `getDocuments(): Promise`_ 114 | 115 | Access Operations and Fragments wrapped with `Source` class based on provided information (in `documents` property of project's configuration). 116 | 117 | ### `getDocumentsSync()` 118 | 119 | _type: `getDocumentsSync(): Source[]`_ 120 | 121 | Access Operations and Fragments wrapped with `Source` class based on provided information (in `documents` property of project's configuration). 122 | 123 | ### `match()` 124 | 125 | _type: `match(filepath: string): boolean`_ 126 | 127 | Checks if the file belongs to the project. It considers `schema`, `documents`, `include` and `exclude` options to see if the file path matches one of those values. 128 | -------------------------------------------------------------------------------- /website/src/content/user/usage.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from '@theguild/components' 2 | 3 | # Usage 4 | 5 | ## Config Search Places 6 | 7 | - `graphql.config.ts` 8 | - `graphql.config.cts` 9 | - `graphql.config.mts` 10 | - `graphql.config.js` 11 | - `graphql.config.cjs` 12 | - `graphql.config.mjs` 13 | - `graphql.config.json` 14 | - `graphql.config.yaml` 15 | - `graphql.config.yml` 16 | - `graphql.config.toml` 17 | 18 | - `.graphqlrc` _(YAML and JSON)_ 19 | - `.graphqlrc.ts` 20 | - `.graphqlrc.cts` 21 | - `.graphqlrc.mts` 22 | - `.graphqlrc.js` 23 | - `.graphqlrc.cjs` 24 | - `.graphqlrc.mjs` 25 | - `.graphqlrc.json` 26 | - `.graphqlrc.yml` 27 | - `.graphqlrc.yaml` 28 | - `.graphqlrc.toml` 29 | 30 | - `graphql` property in `package.json` 31 | 32 | ## Schema 33 | 34 | The simplest config specifies only `schema` which points to the source of GraphQL Schema. 35 | 36 | ```yaml 37 | schema: ./schema.graphql 38 | ``` 39 | 40 | Based on the above example you may think GraphQL Config accepts only single GraphQL files, but it does more than that. 41 | 42 | To learn more about possible sources of GraphQL schema read the ["Specifying schema" chapter](./schema). 43 | 44 | ## Documents 45 | 46 | Another piece of GraphQL may be operations and fragments. In GraphQL Config we call them `documents`. 47 | 48 | ```yaml 49 | # ... 50 | documents: src/components/**/*.graphql 51 | ``` 52 | 53 | By default, GraphQL Config can find and extract documents from GraphQL files but if you want to extend it with JavaScript and TypeScript files (also `tsx` and `jsx`) please read the ["Specifying documents" chapter](./documents). 54 | 55 | ## Include / Exclude 56 | 57 | When you want to point out files related to the schema (for instance, React components) and make your IDE GraphQL Extension recognize those files, you can `include` and `exclude` items: 58 | 59 | ```yaml 60 | # ... 61 | include: src/components/**/*.jsx 62 | exclude: src/components/__ignored__/**/*.jsx 63 | ``` 64 | 65 | Remember that all files specified in `schema` or `documents` are included by default. 66 | 67 | ## Extensions 68 | 69 | To pass information to GraphQL Config's consumers (like IDE extensions, and Node libraries), you can use an `extensions` section that is a key-value object. 70 | 71 | ```yaml 72 | schema: './schema/*.graphql' 73 | extensions: 74 | codegen: 75 | generates: 76 | ./src/types.ts: 77 | plugins: 78 | - typescript 79 | - typescript-resolvers 80 | ``` 81 | 82 | Now [GraphQL Code Generator](https://graphql-code-generator.com) can consume that data. 83 | 84 | ## Projects 85 | 86 | GraphQL Config allows you to define multiple projects within the same config file. 87 | 88 | Consider, for instance, writing the following configuration: 89 | 90 | ```yaml 91 | schema: './schema.graphql' 92 | documents: './src/components/**/*.graphql' 93 | ``` 94 | 95 | This creates a singular, default project. To extend configuration to multiple projects, you can use the following approach: 96 | 97 | ```yaml 98 | projects: 99 | foo: 100 | schema: './packages/foo/schema.graphql' 101 | documents: './packages/foo/src/components/**/*.graphql' 102 | bar: 103 | schema: './packages/bar/schema.graphql' 104 | ``` 105 | 106 | It's also possible to define many projects where one is the default. You can simply add `default` as that project's name: 107 | 108 | ```yaml {2-4} 109 | projects: 110 | default: 111 | schema: './packages/foo/schema.graphql' 112 | documents: './packages/foo/src/components/**/*.graphql' 113 | bar: 114 | schema: './packages/bar/schema.graphql' 115 | ``` 116 | -------------------------------------------------------------------------------- /src/helpers/cosmiconfig.ts: -------------------------------------------------------------------------------- 1 | import { cosmiconfig, cosmiconfigSync, Loader, defaultLoaders } from 'cosmiconfig'; 2 | import { env } from 'string-env-interpolation'; 3 | import { createJiti } from 'jiti'; 4 | 5 | export interface ConfigSearchResult { 6 | config: any; 7 | filepath: string; 8 | isEmpty?: boolean; 9 | } 10 | 11 | const legacySearchPlaces = [ 12 | '.graphqlconfig', 13 | '.graphqlconfig.json', 14 | '.graphqlconfig.yaml', 15 | '.graphqlconfig.yml', 16 | ] as const; 17 | 18 | export function isLegacyConfig(filePath: string): boolean { 19 | filePath = filePath.toLowerCase(); 20 | return legacySearchPlaces.some((name) => filePath.endsWith(name)); 21 | } 22 | 23 | function transformContent(content: string): string { 24 | return env(content); 25 | } 26 | 27 | function createCustomLoader(loader: Loader): Loader { 28 | return (filePath, content) => loader(filePath, transformContent(content)); 29 | } 30 | 31 | export function createCosmiConfig(moduleName: string, legacy: boolean) { 32 | const options = prepareCosmiconfig(moduleName, legacy, 'async'); 33 | return cosmiconfig(moduleName, options); 34 | } 35 | 36 | export function createCosmiConfigSync(moduleName: string, legacy: boolean) { 37 | const options = prepareCosmiconfig(moduleName, legacy, 'sync'); 38 | return cosmiconfigSync(moduleName, options); 39 | } 40 | 41 | const loadTypeScript: Loader = (filepath) => { 42 | const jiti = createJiti(__filename, { interopDefault: true }); 43 | return jiti.import(filepath); 44 | }; 45 | 46 | const loadTypeScriptSync: Loader = (filepath) => { 47 | const jiti = createJiti(__filename, { interopDefault: true }); 48 | return jiti(filepath); 49 | }; 50 | 51 | const loadToml: Loader = (...args) => { 52 | // eslint-disable-next-line @typescript-eslint/no-require-imports 53 | const { loadToml } = require('cosmiconfig-toml-loader'); 54 | return createCustomLoader(loadToml)(...args); 55 | }; 56 | 57 | const loadYaml = createCustomLoader(defaultLoaders['.yaml']); 58 | 59 | function prepareCosmiconfig( 60 | moduleName: string, 61 | legacy: boolean, 62 | mode: 'sync' | 'async', 63 | ): { 64 | searchPlaces: string[]; 65 | loaders: Record; 66 | } { 67 | const searchPlaces = [ 68 | '#.config.ts', 69 | '#.config.cts', 70 | '#.config.mts', 71 | '#.config.js', 72 | '#.config.cjs', 73 | '#.config.mjs', 74 | '#.config.json', 75 | '#.config.yaml', 76 | '#.config.yml', 77 | '#.config.toml', 78 | '.#rc', 79 | '.#rc.ts', 80 | '.#rc.cts', 81 | '.#rc.mts', 82 | '.#rc.js', 83 | '.#rc.cjs', 84 | '.#rc.mjs', 85 | '.#rc.json', 86 | '.#rc.yml', 87 | '.#rc.yaml', 88 | '.#rc.toml', 89 | 'package.json', 90 | ]; 91 | 92 | if (legacy) { 93 | searchPlaces.push(...legacySearchPlaces); 94 | } 95 | 96 | // We need to wrap loaders in order to access and transform file content (as string) 97 | // Cosmiconfig has transform option but at this point config is not a string but an object 98 | return { 99 | searchPlaces: searchPlaces.map((place) => place.replace('#', moduleName)), 100 | loaders: { 101 | '.ts': mode === 'async' ? loadTypeScript : loadTypeScriptSync, 102 | '.cts': mode === 'async' ? loadTypeScript : loadTypeScriptSync, 103 | '.mts': mode === 'async' ? loadTypeScript : loadTypeScriptSync, 104 | '.js': mode === 'async' ? loadTypeScript : loadTypeScriptSync, 105 | '.mjs': mode === 'async' ? loadTypeScript : loadTypeScriptSync, 106 | '.json': defaultLoaders['.json'], 107 | '.yaml': loadYaml, 108 | '.yml': loadYaml, 109 | '.toml': loadToml, 110 | noExt: loadYaml, 111 | }, 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-config", 3 | "version": "5.1.5", 4 | "description": "The easiest way to configure your development environment with your GraphQL schema (supported by most tools, editors & IDEs)", 5 | "sideEffects": false, 6 | "main": "dist/cjs/index.js", 7 | "module": "dist/esm/index.js", 8 | "packageManager": "pnpm@10.13.1", 9 | "exports": { 10 | ".": { 11 | "require": { 12 | "types": "./dist/typings/index.d.cts", 13 | "default": "./dist/cjs/index.js" 14 | }, 15 | "import": { 16 | "types": "./dist/typings/index.d.ts", 17 | "default": "./dist/esm/index.js" 18 | }, 19 | "default": { 20 | "types": "./dist/typings/index.d.ts", 21 | "default": "./dist/esm/index.js" 22 | } 23 | }, 24 | "./package.json": "./package.json" 25 | }, 26 | "typings": "dist/typings/index.d.ts", 27 | "typescript": { 28 | "definition": "dist/typings/index.d.ts" 29 | }, 30 | "publishConfig": { 31 | "directory": "dist", 32 | "access": "public" 33 | }, 34 | "type": "module", 35 | "scripts": { 36 | "prepublishOnly": "pnpm run build", 37 | "clean": "rimraf dist", 38 | "prebuild": "pnpm run clean && pnpm run json-schema", 39 | "postbuild": "tsx scripts/postbuild.ts", 40 | "build": "bob build", 41 | "prettier": "prettier --cache --write --list-different .", 42 | "prettier:check": "prettier --cache --check .", 43 | "lint": "ESLINT_USE_FLAT_CONFIG=false eslint --cache --cache-location node_modules/.cache/.eslintcache --ignore-path .gitignore .", 44 | "ci:lint": "ESLINT_USE_FLAT_CONFIG=false eslint --cache --cache-location node_modules/.cache/.eslintcache --ignore-path .gitignore . --output-file eslint_report.json --format json", 45 | "test": "vitest .", 46 | "prerelease": "pnpm run build", 47 | "release": "changeset publish", 48 | "json-schema": "typescript-json-schema src/types.ts IGraphQLConfig --out config-schema.json --ignoreErrors --required --titles && prettier --write config-schema.json" 49 | }, 50 | "peerDependencies": { 51 | "cosmiconfig-toml-loader": "^1.0.0", 52 | "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 53 | }, 54 | "peerDependenciesMeta": { 55 | "cosmiconfig-toml-loader": { 56 | "optional": true 57 | } 58 | }, 59 | "dependencies": { 60 | "@graphql-tools/graphql-file-loader": "^8.0.0", 61 | "@graphql-tools/json-file-loader": "^8.0.0", 62 | "@graphql-tools/load": "^8.1.0", 63 | "@graphql-tools/merge": "^9.0.0", 64 | "@graphql-tools/url-loader": "^8.0.0", 65 | "@graphql-tools/utils": "^10.0.0", 66 | "cosmiconfig": "^8.1.0", 67 | "jiti": "^2.0.0", 68 | "minimatch": "^9.0.5", 69 | "string-env-interpolation": "^1.0.1", 70 | "tslib": "^2.4.0" 71 | }, 72 | "devDependencies": { 73 | "@changesets/changelog-github": "0.5.1", 74 | "@changesets/cli": "2.29.8", 75 | "@types/node": "22.14.0", 76 | "@typescript-eslint/eslint-plugin": "8.38.0", 77 | "@typescript-eslint/parser": "8.38.0", 78 | "bob-the-bundler": "7.0.1", 79 | "cosmiconfig-toml-loader": "1.0.0", 80 | "del": "8.0.0", 81 | "eslint": "9.24.0", 82 | "eslint-config-prettier": "10.1.8", 83 | "graphql": "16.12.0", 84 | "husky": "9.1.7", 85 | "lint-staged": "15.5.2", 86 | "make-dir": "5.0.0", 87 | "parent-module": "3.1.0", 88 | "prettier": "3.5.3", 89 | "rimraf": "6.0.1", 90 | "ts-node": "10.9.2", 91 | "tsx": "4.19.3", 92 | "typescript": "5.8.3", 93 | "typescript-json-schema": "0.65.1", 94 | "vite-tsconfig-paths": "^5.0.0", 95 | "vitest": "3.1.1" 96 | }, 97 | "repository": { 98 | "type": "git", 99 | "url": "https://github.com/kamilkisiela/graphql-config.git" 100 | }, 101 | "homepage": "https://graphql-config.com", 102 | "keywords": [ 103 | "graphql", 104 | "config", 105 | "relay", 106 | "apollo" 107 | ], 108 | "author": { 109 | "email": "kamil.kisiela@gmail.com", 110 | "name": "Kamil Kisiela", 111 | "url": "https://github.com/kamilkisiela" 112 | }, 113 | "license": "MIT", 114 | "bob": { 115 | "build": { 116 | "copy": [ 117 | "config-schema.json" 118 | ] 119 | } 120 | }, 121 | "lint-staged": { 122 | "{src,test}/**/*": [ 123 | "prettier --write" 124 | ] 125 | }, 126 | "husky": { 127 | "hooks": { 128 | "pre-commit": "lint-staged" 129 | } 130 | }, 131 | "engines": { 132 | "node": ">= 16.0.0", 133 | "pnpm": "^10.0.0" 134 | }, 135 | "pnpm": { 136 | "onlyBuiltDependencies": [ 137 | "esbuild" 138 | ], 139 | "overrides": { 140 | "glob": "11.1.0" 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /website/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { CallToAction, cn, ConfigLogo, GitHubIcon, Hero, InfoCard, ToolsAndLibrariesCards } from '@theguild/components'; 2 | import { metadata as rootMetadata } from './layout'; 3 | 4 | export const metadata = { 5 | alternates: { 6 | // to remove leading slash 7 | canonical: '.', 8 | }, 9 | openGraph: { 10 | ...rootMetadata!.openGraph, 11 | // to remove leading slash 12 | url: '.', 13 | }, 14 | }; 15 | 16 | export default function IndexPage() { 17 | return ( 18 |
19 | } 23 | checkmarks={['Fully open source', 'No vendor lock']} 24 | // Original logo has some issues with overflowing elements 25 | className="[&_.-z-10>svg]:fill-[#B0CBD1]" 26 | > 27 | 28 | Get started 29 | 30 | 31 | Changelog 32 | 33 | 34 | 35 | GitHub 36 | 37 | 38 | 39 | 40 |
41 | ); 42 | } 43 | 44 | function CardsSection({ className }: { className?: string }) { 45 | return ( 46 |
47 |
    48 | 53 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | } 66 | className="rounded-2xl md:rounded-3xl" 67 | > 68 | You gain simplicity and a central place to setup your tools 69 | 70 | 75 | 76 | 77 | } 78 | className="rounded-2xl md:rounded-3xl" 79 | > 80 | Highly customizable and extensible 81 | 82 | 98 | 99 | 100 | 101 | } 102 | className="rounded-2xl md:rounded-3xl" 103 | > 104 | A standard in the community 105 | 106 |
107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /test/loaders.spec.ts: -------------------------------------------------------------------------------- 1 | import { DirectiveDefinitionNode, buildSchema, GraphQLSchema, Kind } from 'graphql'; 2 | import { Loader, Source } from '@graphql-tools/utils'; 3 | import { LoadersRegistry } from 'graphql-config'; 4 | import { Mock } from 'vitest'; 5 | import { loadTypedefsSync, loadSchemaSync, loadSchema, LoadSchemaOptions } from '@graphql-tools/load'; 6 | 7 | vi.mock('@graphql-tools/load', async () => { 8 | const { parse, buildSchema } = await import('graphql'); 9 | const document = parse(/* GraphQL */ ` 10 | type Query { 11 | foo: String @cache 12 | } 13 | `); 14 | 15 | const schema = buildSchema(/* GraphQL */ ` 16 | type Query { 17 | foo: String 18 | } 19 | `); 20 | 21 | // @ts-expect-error - we're adding a property here 22 | schema.isTheOne = true; 23 | 24 | const { OPERATION_KINDS } = await vi.importActual('@graphql-tools/load'); 25 | 26 | return { 27 | OPERATION_KINDS, 28 | loadTypedefs: vi.fn(() => [{ document }]), 29 | loadTypedefsSync: vi.fn(() => [{ document }]), 30 | loadSchemaSync: vi.fn(() => schema), 31 | loadSchema: vi.fn(() => schema), 32 | }; 33 | }); 34 | 35 | describe('middlewares', () => { 36 | test('loads Sources instead of GraphQLSchema when middlewares are defined', () => { 37 | const registry = new LoadersRegistry({ cwd: __dirname }); 38 | 39 | const cacheDirective: DirectiveDefinitionNode = { 40 | kind: Kind.DIRECTIVE_DEFINITION, 41 | name: { 42 | kind: Kind.NAME, 43 | value: 'cache', 44 | }, 45 | repeatable: false, 46 | locations: [ 47 | { 48 | kind: Kind.NAME, 49 | value: 'FIELD_DEFINITION', 50 | }, 51 | ], 52 | }; 53 | 54 | registry.use((doc) => ({ 55 | ...doc, 56 | definitions: [...doc.definitions, cacheDirective], 57 | })); 58 | 59 | const schema = registry.loadSchemaSync('anything'); 60 | 61 | expect(schema.getDirective('cache')).toBeDefined(); 62 | }); 63 | 64 | test('no middlewares means we load GraphQLSchema directly', async () => { 65 | const registry = new LoadersRegistry({ cwd: __dirname }); 66 | 67 | const received = registry.loadSchemaSync('anything'); 68 | const receivedAsync = await registry.loadSchema('anything'); 69 | 70 | // @ts-expect-error - we're adding a property here 71 | expect(received.isTheOne).toEqual(true); 72 | // @ts-expect-error - we're adding a property here 73 | expect(receivedAsync.isTheOne).toEqual(true); 74 | }); 75 | }); 76 | 77 | class CustomLoader implements Loader { 78 | constructor(private schema: GraphQLSchema) {} 79 | 80 | loaderId(): string { 81 | return 'custom'; 82 | } 83 | 84 | async canLoad(): Promise { 85 | return true; 86 | } 87 | 88 | canLoadSync(): boolean { 89 | return true; 90 | } 91 | 92 | async load(): Promise { 93 | return [{ schema: this.schema }]; 94 | } 95 | 96 | loadSync(): Source[] { 97 | return [{ schema: this.schema }]; 98 | } 99 | } 100 | 101 | const differentSchema = buildSchema(/* GraphQL */ ` 102 | type Query { 103 | bar: String 104 | } 105 | `); 106 | 107 | describe('override', () => { 108 | beforeAll(async () => { 109 | const load = await vi.importActual('@graphql-tools/load'); 110 | (loadTypedefsSync as Mock).mockImplementation(load.loadTypedefsSync); 111 | (loadSchemaSync as Mock).mockImplementation(load.loadSchemaSync); 112 | (loadSchema as Mock).mockImplementation(load.loadSchema); 113 | }); 114 | 115 | test('overrides default loaders', async () => { 116 | const registry = new LoadersRegistry({ cwd: __dirname }); 117 | 118 | registry.override([new CustomLoader(differentSchema)]); 119 | 120 | const received = registry.loadSchemaSync('anything'); 121 | const receivedAsync = await registry.loadSchema('anything'); 122 | 123 | expect(received.getQueryType().getFields().bar).toBeDefined(); 124 | expect(receivedAsync.getQueryType().getFields().bar).toBeDefined(); 125 | }); 126 | 127 | test('allows custom loader options', async () => { 128 | const registry = new LoadersRegistry({ cwd: __dirname }); 129 | const customOptions = { assumeValidSDL: true } as Partial; 130 | const customLoader = new CustomLoader(differentSchema); 131 | const expectedOptions = { 132 | ...customOptions, 133 | cwd: __dirname, 134 | loaders: [customLoader], 135 | }; 136 | 137 | registry.override([customLoader]); 138 | 139 | const received = registry.loadSchemaSync('anything', null, customOptions); 140 | const receivedAsync = await registry.loadSchema('anything', null, customOptions); 141 | 142 | expect(received.getQueryType().getFields().bar).toBeDefined(); 143 | expect(receivedAsync.getQueryType().getFields().bar).toBeDefined(); 144 | expect(loadSchema).toBeCalledWith('anything', expectedOptions); 145 | 146 | expect(loadSchemaSync).toBeCalledWith('anything', expectedOptions); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /config-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "anyOf": [ 4 | { 5 | "$ref": "#/definitions/IGraphQLProjects" 6 | }, 7 | { 8 | "$ref": "#/definitions/IGraphQLProject" 9 | }, 10 | { 11 | "$ref": "#/definitions/IGraphQLProjectLegacy" 12 | } 13 | ], 14 | "definitions": { 15 | "DocumentPointer": { 16 | "anyOf": [ 17 | { 18 | "items": { 19 | "type": "string" 20 | }, 21 | "type": "array" 22 | }, 23 | { 24 | "type": "string" 25 | } 26 | ], 27 | "title": "DocumentPointer" 28 | }, 29 | "IExtensions": { 30 | "additionalProperties": {}, 31 | "description": "Configuration of each used extension", 32 | "title": "IExtensions", 33 | "type": "object" 34 | }, 35 | "IGraphQLProject": { 36 | "description": "GraphQL Project", 37 | "properties": { 38 | "documents": { 39 | "$ref": "#/definitions/DocumentPointer", 40 | "title": "documents" 41 | }, 42 | "exclude": { 43 | "$ref": "#/definitions/WithList", 44 | "title": "exclude" 45 | }, 46 | "extensions": { 47 | "$ref": "#/definitions/IExtensions", 48 | "title": "extensions" 49 | }, 50 | "include": { 51 | "$ref": "#/definitions/WithList", 52 | "title": "include" 53 | }, 54 | "schema": { 55 | "$ref": "#/definitions/SchemaPointer", 56 | "title": "schema" 57 | } 58 | }, 59 | "required": ["schema"], 60 | "title": "IGraphQLProject", 61 | "type": "object" 62 | }, 63 | "IGraphQLProjectLegacy": { 64 | "description": "Legacy structure of GraphQL Config v2", 65 | "properties": { 66 | "excludes": { 67 | "items": { 68 | "type": "string" 69 | }, 70 | "title": "excludes", 71 | "type": "array" 72 | }, 73 | "extensions": { 74 | "$ref": "#/definitions/IExtensions", 75 | "title": "extensions" 76 | }, 77 | "includes": { 78 | "items": { 79 | "type": "string" 80 | }, 81 | "title": "includes", 82 | "type": "array" 83 | }, 84 | "schemaPath": { 85 | "title": "schemaPath", 86 | "type": "string" 87 | } 88 | }, 89 | "required": ["schemaPath"], 90 | "title": "IGraphQLProjectLegacy", 91 | "type": "object" 92 | }, 93 | "IGraphQLProjects": { 94 | "description": "Multiple named projects", 95 | "properties": { 96 | "projects": { 97 | "additionalProperties": { 98 | "anyOf": [ 99 | { 100 | "$ref": "#/definitions/IGraphQLProject" 101 | }, 102 | { 103 | "$ref": "#/definitions/IGraphQLProjectLegacy" 104 | } 105 | ] 106 | }, 107 | "title": "projects", 108 | "type": "object" 109 | } 110 | }, 111 | "required": ["projects"], 112 | "title": "IGraphQLProjects", 113 | "type": "object" 114 | }, 115 | "SchemaPointer": { 116 | "anyOf": [ 117 | { 118 | "additionalProperties": { 119 | "properties": { 120 | "headers": { 121 | "additionalProperties": { 122 | "type": "string" 123 | }, 124 | "title": "headers", 125 | "type": "object" 126 | } 127 | }, 128 | "required": ["headers"], 129 | "type": "object" 130 | }, 131 | "type": "object" 132 | }, 133 | { 134 | "items": { 135 | "anyOf": [ 136 | { 137 | "additionalProperties": { 138 | "properties": { 139 | "headers": { 140 | "additionalProperties": { 141 | "type": "string" 142 | }, 143 | "title": "headers", 144 | "type": "object" 145 | } 146 | }, 147 | "required": ["headers"], 148 | "type": "object" 149 | }, 150 | "type": "object" 151 | }, 152 | { 153 | "type": "string" 154 | } 155 | ] 156 | }, 157 | "type": "array" 158 | }, 159 | { 160 | "type": "string" 161 | } 162 | ], 163 | "title": "SchemaPointer" 164 | }, 165 | "WithList": { 166 | "anyOf": [ 167 | { 168 | "items": { 169 | "type": "string" 170 | }, 171 | "type": "array" 172 | }, 173 | { 174 | "type": "string" 175 | } 176 | ], 177 | "title": "WithList" 178 | } 179 | }, 180 | "description": "Structure of GraphQL Config" 181 | } 182 | -------------------------------------------------------------------------------- /src/loaders.ts: -------------------------------------------------------------------------------- 1 | import { Source, Loader } from '@graphql-tools/utils'; 2 | import { 3 | loadSchema, 4 | loadSchemaSync, 5 | loadTypedefs, 6 | loadTypedefsSync, 7 | loadDocuments, 8 | loadDocumentsSync, 9 | UnnormalizedTypeDefPointer, 10 | LoadTypedefsOptions as ToolsLoadTypedefsOptions, 11 | LoadSchemaOptions as ToolsLoadSchemaOptions, 12 | OPERATION_KINDS, 13 | } from '@graphql-tools/load'; 14 | import { mergeTypeDefs } from '@graphql-tools/merge'; 15 | import { GraphQLSchema, DocumentNode, buildASTSchema, print } from 'graphql'; 16 | import { MiddlewareFn, useMiddleware } from './helpers/index.js'; 17 | 18 | type Pointer = UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[]; 19 | type LoadTypedefsOptions = Partial; 20 | type LoadSchemaOptions = Partial; 21 | export type SchemaOutput = 'GraphQLSchema' | 'DocumentNode' | 'string'; 22 | 23 | export class LoadersRegistry { 24 | private _loaders = new Set(); 25 | private _middlewares: MiddlewareFn[] = []; 26 | private readonly cwd: string; 27 | 28 | constructor({ cwd }: { cwd: string }) { 29 | this.cwd = cwd; 30 | } 31 | 32 | register(loader: Loader): void { 33 | this._loaders.add(loader); 34 | } 35 | 36 | override(loaders: Loader[]): void { 37 | this._loaders = new Set(loaders); 38 | } 39 | 40 | use(middleware: MiddlewareFn): void { 41 | this._middlewares.push(middleware); 42 | } 43 | 44 | async loadTypeDefs(pointer: Pointer, options?: LoadTypedefsOptions): Promise { 45 | return loadTypedefs(pointer, { 46 | loaders: Array.from(this._loaders), 47 | cwd: this.cwd, 48 | ...options, 49 | }); 50 | } 51 | 52 | loadTypeDefsSync(pointer: Pointer, options?: LoadTypedefsOptions): Source[] { 53 | return loadTypedefsSync(pointer, this.createOptions(options)); 54 | } 55 | 56 | async loadDocuments(pointer: Pointer, options?: LoadTypedefsOptions): Promise { 57 | return loadDocuments(pointer, this.createOptions(options)); 58 | } 59 | 60 | loadDocumentsSync(pointer: Pointer, options?: LoadTypedefsOptions): Source[] { 61 | return loadDocumentsSync(pointer, this.createOptions(options)); 62 | } 63 | 64 | async loadSchema(pointer: Pointer): Promise; 65 | async loadSchema(pointer: Pointer, out: 'string', options?: LoadSchemaOptions): Promise; 66 | async loadSchema(pointer: Pointer, out: 'DocumentNode', options?: LoadSchemaOptions): Promise; 67 | async loadSchema(pointer: Pointer, out: 'GraphQLSchema', options?: LoadSchemaOptions): Promise; 68 | async loadSchema( 69 | pointer: Pointer, 70 | out?: SchemaOutput, 71 | options?: LoadSchemaOptions, 72 | ): Promise { 73 | out = out || 'GraphQLSchema'; 74 | const loadSchemaOptions = this.createOptions(options); 75 | 76 | if (out === 'GraphQLSchema' && !this._middlewares.length) { 77 | return loadSchema(pointer, loadSchemaOptions); 78 | } 79 | 80 | const schemaDoc = this.transformSchemaSources( 81 | await loadTypedefs(pointer, { 82 | filterKinds: OPERATION_KINDS, 83 | ...loadSchemaOptions, 84 | }), 85 | ); 86 | 87 | // TODO: TS screams about `out` not being compatible with SchemaOutput 88 | return this.castSchema(schemaDoc, out as any); 89 | } 90 | 91 | loadSchemaSync(pointer: Pointer): GraphQLSchema; 92 | loadSchemaSync(pointer: Pointer, out: 'string', options?: LoadSchemaOptions): GraphQLSchema; 93 | loadSchemaSync(pointer: Pointer, out: 'DocumentNode', options?: LoadSchemaOptions): DocumentNode; 94 | loadSchemaSync(pointer: Pointer, out: 'GraphQLSchema', options?: LoadSchemaOptions): GraphQLSchema; 95 | loadSchemaSync( 96 | pointer: Pointer, 97 | out?: SchemaOutput, 98 | options?: LoadSchemaOptions, 99 | ): GraphQLSchema | DocumentNode | string { 100 | out = out || 'GraphQLSchema'; 101 | const loadSchemaOptions = this.createOptions(options); 102 | 103 | if (out === 'GraphQLSchema' && !this._middlewares.length) { 104 | return loadSchemaSync(pointer, loadSchemaOptions); 105 | } 106 | 107 | const schemaDoc = this.transformSchemaSources( 108 | loadTypedefsSync(pointer, { 109 | filterKinds: OPERATION_KINDS, 110 | ...loadSchemaOptions, 111 | }), 112 | ); 113 | 114 | return this.castSchema(schemaDoc, out as any); 115 | } 116 | 117 | private createOptions(options?: T) { 118 | return { 119 | loaders: Array.from(this._loaders), 120 | cwd: this.cwd, 121 | ...options, 122 | }; 123 | } 124 | 125 | private transformSchemaSources(sources: Source[]) { 126 | const documents: DocumentNode[] = sources.map((source) => source.document); 127 | const document = mergeTypeDefs(documents); 128 | 129 | return useMiddleware(this._middlewares)(document); 130 | } 131 | 132 | private castSchema(doc: DocumentNode, out: 'string'): string; 133 | private castSchema(doc: DocumentNode, out: 'DocumentNode'): DocumentNode; 134 | private castSchema(doc: DocumentNode, out: 'GraphQLSchema'): GraphQLSchema; 135 | private castSchema(doc: DocumentNode, out: SchemaOutput): string | DocumentNode | GraphQLSchema { 136 | if (out === 'DocumentNode') { 137 | return doc; 138 | } 139 | 140 | if (out === 'GraphQLSchema') { 141 | return buildASTSchema(doc); 142 | } 143 | 144 | return print(doc); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { dirname } from 'path'; 2 | import type { IGraphQLConfig, GraphQLConfigResult } from './types.js'; 3 | import { GraphQLProjectConfig } from './project-config.js'; 4 | import { 5 | isMultipleProjectConfig, 6 | isSingleProjectConfig, 7 | findConfig, 8 | getConfig, 9 | getConfigSync, 10 | findConfigSync, 11 | isLegacyProjectConfig, 12 | } from './helpers/index.js'; 13 | import { ProjectNotFoundError, ConfigNotFoundError, ConfigEmptyError } from './errors.js'; 14 | import { GraphQLExtensionDeclaration, GraphQLExtensionsRegistry } from './extension.js'; 15 | import { EndpointsExtension } from './extensions/endpoints.js'; 16 | import { isLegacyConfig } from './helpers/cosmiconfig.js'; 17 | 18 | const CWD = process.cwd(); 19 | const defaultConfigName = 'graphql'; 20 | 21 | interface LoadConfigOptions { 22 | filepath?: string; 23 | rootDir?: string; 24 | extensions?: GraphQLExtensionDeclaration[]; 25 | throwOnMissing?: boolean; 26 | throwOnEmpty?: boolean; 27 | configName?: string; 28 | legacy?: boolean; 29 | } 30 | 31 | const defaultLoadConfigOptions: LoadConfigOptions = { 32 | rootDir: CWD, 33 | extensions: [], 34 | throwOnMissing: true, 35 | throwOnEmpty: true, 36 | configName: defaultConfigName, 37 | legacy: true, 38 | }; 39 | 40 | export async function loadConfig(options: LoadConfigOptions): Promise { 41 | const { filepath, configName, rootDir, extensions, throwOnEmpty, throwOnMissing, legacy } = { 42 | ...defaultLoadConfigOptions, 43 | ...options, 44 | }; 45 | 46 | try { 47 | const found = filepath 48 | ? await getConfig({ filepath, configName, legacy }) 49 | : await findConfig({ rootDir, configName, legacy }); 50 | 51 | return new GraphQLConfig(found, extensions); 52 | } catch (error) { 53 | return handleError(error, { throwOnMissing, throwOnEmpty }); 54 | } 55 | } 56 | 57 | export function loadConfigSync(options: LoadConfigOptions) { 58 | const { filepath, configName, rootDir, extensions, throwOnEmpty, throwOnMissing, legacy } = { 59 | ...defaultLoadConfigOptions, 60 | ...options, 61 | }; 62 | 63 | try { 64 | const found = filepath 65 | ? getConfigSync({ filepath, configName, legacy }) 66 | : findConfigSync({ rootDir, configName, legacy }); 67 | 68 | return new GraphQLConfig(found, extensions); 69 | } catch (error) { 70 | return handleError(error, { throwOnMissing, throwOnEmpty }); 71 | } 72 | } 73 | 74 | function handleError( 75 | error: any, 76 | options: Pick, 77 | ): never | undefined { 78 | if ( 79 | (!options.throwOnMissing && error instanceof ConfigNotFoundError) || 80 | (!options.throwOnEmpty && error instanceof ConfigEmptyError) 81 | ) { 82 | return; 83 | } 84 | 85 | throw error; 86 | } 87 | 88 | export class GraphQLConfig { 89 | private readonly _rawConfig: IGraphQLConfig; 90 | readonly filepath: string; 91 | readonly dirpath: string; 92 | // TODO: in v5 change projects to `Object.create(null)` and refactor `graphql-codegen-cli` to remove `projects.hasOwnProperty` 93 | // https://github.com/dotansimha/graphql-code-generator/blob/3c6abbde7a20515d9a1d55b4003ef365d248efb5/packages/graphql-codegen-cli/src/graphql-config.ts#L62-L72 94 | readonly projects: Record = {}; 95 | readonly extensions: GraphQLExtensionsRegistry; 96 | 97 | constructor(raw: GraphQLConfigResult, extensions: GraphQLExtensionDeclaration[]) { 98 | this._rawConfig = raw.config; 99 | this.filepath = raw.filepath; 100 | this.dirpath = dirname(raw.filepath); 101 | this.extensions = new GraphQLExtensionsRegistry({ cwd: this.dirpath }); 102 | 103 | // Register Endpoints 104 | this.extensions.register(EndpointsExtension); 105 | 106 | for (const extension of extensions) { 107 | this.extensions.register(extension); 108 | } 109 | 110 | if (isMultipleProjectConfig(this._rawConfig)) { 111 | for (const [projectName, config] of Object.entries(this._rawConfig.projects)) { 112 | this.projects[projectName] = new GraphQLProjectConfig({ 113 | filepath: this.filepath, 114 | name: projectName, 115 | config, 116 | extensionsRegistry: this.extensions, 117 | }); 118 | } 119 | } else if (isSingleProjectConfig(this._rawConfig) || isLegacyProjectConfig(this._rawConfig)) { 120 | this.projects.default = new GraphQLProjectConfig({ 121 | filepath: this.filepath, 122 | name: 'default', 123 | config: this._rawConfig, 124 | extensionsRegistry: this.extensions, 125 | }); 126 | } 127 | } 128 | 129 | getProject(name?: string): GraphQLProjectConfig | never { 130 | if (!name) { 131 | return this.getDefault(); 132 | } 133 | 134 | const project = this.projects[name]; 135 | 136 | if (!project) { 137 | throw new ProjectNotFoundError(`Project '${name}' not found`); 138 | } 139 | 140 | return project; 141 | } 142 | 143 | getProjectForFile(filepath: string): GraphQLProjectConfig | never { 144 | // Looks for a project that includes the file or the file is a part of schema or documents 145 | for (const project of Object.values(this.projects)) { 146 | if (project.match(filepath)) { 147 | return project; 148 | } 149 | } 150 | 151 | // The file doesn't match any of the project 152 | // Looks for a first project that has no `include` and `exclude` 153 | for (const project of Object.values(this.projects)) { 154 | if (!project.include && !project.exclude) { 155 | return project; 156 | } 157 | } 158 | 159 | throw new ProjectNotFoundError(`File '${filepath}' doesn't match any project`); 160 | } 161 | 162 | getDefault(): GraphQLProjectConfig | never { 163 | return this.getProject('default'); 164 | } 165 | 166 | isLegacy(): boolean { 167 | return isLegacyConfig(this.filepath); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /website/src/content/library/extensions.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from '@theguild/components' 2 | 3 | # Writing and Consuming Extensions 4 | 5 | The main purpose of GraphQL Config Extensions is to pass information to extension's consumer to extend the behavior of GraphQL Config's logic. 6 | 7 | GraphQL Config ships TypeScript declaration files so let's make use of them in the following examples. 8 | 9 | ## How to Write Extensions 10 | 11 | To make sure you write extensions correctly, import and use the `GraphQLExtensionDeclaration` type from `graphql-config` package. Thanks to TypeScript, you get autocompletion and in-editor validation. 12 | 13 | The main requirement of an extension is its name. Providing a name lets GraphQL Config match the extension with its namespace in the config file. 14 | 15 | ```ts 16 | import { GraphQLExtensionDeclaration } from 'graphql-config' 17 | 18 | const InspectorExtension: GraphQLExtensionDeclaration = api => { 19 | return { 20 | name: 'inspector' 21 | } 22 | } 23 | ``` 24 | 25 | ### Schema Middlewares 26 | 27 | GraphQL Config lets you intercept the GraphQL Schema loading process which may be helpful when dealing with custom directives like in Relay or Apollo Federation. We call it Middlewares. 28 | 29 | ```ts 30 | import { GraphQLExtensionDeclaration } from 'graphql-config' 31 | 32 | const RelayExtension: GraphQLExtensionDeclaration = api => { 33 | api.loaders.schema.use(document => { 34 | // The middleware receives a DocumentNode object 35 | // Adds relay directives 36 | // Returns a new DocumentNode 37 | return addRelayToDocumentNode(document) 38 | }) 39 | 40 | return { 41 | name: 'relay' 42 | } 43 | } 44 | ``` 45 | 46 | ## Consuming Extension 47 | 48 | As a GraphQL tool author, you will likely want to load the config and register your extension to understand the user's configuration. 49 | 50 | ```ts 51 | import { loadConfig } from 'graphql-config' 52 | import { InspectorExtension } from './extension' 53 | 54 | async function main() { 55 | const config = await loadConfig({ 56 | extensions: [InspectorExtension] 57 | }) 58 | } 59 | ``` 60 | 61 | For synchronous use `loadConfigSync` 62 | 63 | Now that everything is ready, GraphQL Config understands there's the Inspector extension. 64 | 65 | To access information stored in the config file, do the following: 66 | 67 | ```ts 68 | async function main() { 69 | // ... code from previous steps 70 | 71 | // Reads configuration of a default project 72 | const project = config.getDefault() 73 | // Reads configuration of a named project 74 | const project = config.getProject('admin') 75 | 76 | // Reads extension's configuration defined in a project 77 | const inspectorConfig = project.extension('inspector') 78 | 79 | // Given the following config file: 80 | // 81 | // schema: './schema.graphql' 82 | // extensions: 83 | // inspector: 84 | // validate: true 85 | // 86 | 87 | // You're able to get `validate`: 88 | if (inspectorConfig.validate === true) { 89 | // ... 90 | } 91 | } 92 | ``` 93 | 94 | Getting `GraphQLSchema` is straightforward: each project has `getSchema(): Promise{:ts}` method. 95 | 96 | ```ts 97 | async function main() { 98 | // ... code from the previous example 99 | if (inspectorConfig.validate === true) { 100 | const schema = await project.getSchema() 101 | 102 | validateSchema(schema) 103 | } 104 | } 105 | ``` 106 | 107 | GraphQL Config can generate a schema not only as `GraphQLSchema` object but also as a `DocumentNode`. (For more info, read the API reference of [`GraphQLProjectConfig`](./graphql-project-config).) 108 | It's also capable of loading operations and fragments. 109 | 110 | ## Registering Loaders 111 | 112 | In previous examples, we pointed GraphQL Config to the `schema.graphql` file. GraphQL Config, by default, understands the Introspection result stored in JSON file, GraphQL files (`.graphql`, `.gql`, `.graphqls` and `.gqls`) and the document returned by any functioning GraphQL endpoint (specified by URL). 113 | 114 | In some cases, you may want to extend that behavior and teach GraphQL Config how to look for GraphQL SDL (modularized schema for example) across many JavaScript or TypeScript files. 115 | 116 | It's now possible thanks to **loaders**. 117 | 118 | The [GraphQL Tools](https://github.com/ardatan/graphql-tools) library has [a few already written loaders](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders) that GraphQL Config uses. We mentioned the default loaders, but the repo contains a few extra ones. 119 | 120 | For simplicity, we're going to use only [the one](https://github.com/ardatan/graphql-tools/tree/master/packages/loaders/code-file) responsible for extracting GraphQL SDL from code files. 121 | 122 | ```ts 123 | import { GraphQLExtensionDeclaration } from 'graphql-config' 124 | import { CodeFileLoader } from '@graphql-tools/code-file-loader' 125 | 126 | const InspectorExtension: GraphQLExtensionDeclaration = api => { 127 | // For schema 128 | api.loaders.schema.register(new CodeFileLoader()) 129 | // For documents 130 | api.loaders.documents.register(new CodeFileLoader()) 131 | 132 | return { name: 'inspector' } 133 | } 134 | ``` 135 | 136 | Let's say you have GraphQL SDL modularized across multiple TypeScript files, written like this: 137 | 138 | ```ts 139 | import { gql } from 'graphql-tag' 140 | 141 | export const typeDefs = gql` 142 | type User { 143 | id: ID! 144 | name: String! 145 | } 146 | 147 | extend type Query { 148 | user(id: ID!): User! 149 | } 150 | ` 151 | ``` 152 | 153 | With `CodeFileLoader` you can extract those GraphQL pieces: 154 | 155 | ```yaml 156 | schema: './src/modules/*.ts' # uses a glob pattern to look for files 157 | extensions: 158 | inspector: 159 | validate: true 160 | ``` 161 | 162 | There are two kinds of loaders. One is responsible for handling schemas, and the other covers Operations and Fragments (we call them both `Documents`). 163 | 164 | To read more about loaders, please check ["Loaders"](./loaders) chapter. 165 | -------------------------------------------------------------------------------- /src/project-config.ts: -------------------------------------------------------------------------------- 1 | import { dirname, isAbsolute, relative, normalize } from 'path'; 2 | import type { GraphQLSchema, DocumentNode } from 'graphql'; 3 | import type { Source } from '@graphql-tools/utils'; 4 | import { minimatch } from 'minimatch'; 5 | import { 6 | LoadSchemaOptions as ToolsLoadSchemaOptions, 7 | LoadTypedefsOptions as ToolsLoadTypedefsOptions, 8 | UnnormalizedTypeDefPointer, 9 | } from '@graphql-tools/load'; 10 | import { ExtensionMissingError } from './errors.js'; 11 | import type { GraphQLExtensionsRegistry } from './extension.js'; 12 | import type { IExtensions, IGraphQLProject, IGraphQLProjectLegacy, WithList } from './types.js'; 13 | import { isLegacyProjectConfig } from './helpers/index.js'; 14 | import type { SchemaOutput } from './loaders.js'; 15 | 16 | type Pointer = UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[]; 17 | type LoadTypedefsOptions = Partial; 18 | type LoadSchemaOptions = Partial; 19 | 20 | export class GraphQLProjectConfig { 21 | readonly schema: Pointer; 22 | readonly documents?: UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[]; 23 | readonly include?: WithList; 24 | readonly exclude?: WithList; 25 | readonly extensions: IExtensions; 26 | readonly filepath: string; 27 | readonly dirpath: string; 28 | readonly name: string; 29 | readonly isLegacy: boolean; 30 | 31 | private readonly _extensionsRegistry: GraphQLExtensionsRegistry; 32 | 33 | constructor({ 34 | filepath, 35 | name, 36 | config, 37 | extensionsRegistry, 38 | }: { 39 | filepath: string; 40 | name: string; 41 | config: IGraphQLProject | IGraphQLProjectLegacy; 42 | extensionsRegistry: GraphQLExtensionsRegistry; 43 | }) { 44 | this.filepath = filepath; 45 | this.dirpath = dirname(filepath); 46 | this.name = name; 47 | this.extensions = config.extensions || {}; 48 | 49 | if (isLegacyProjectConfig(config)) { 50 | this.schema = config.schemaPath; 51 | this.include = config.includes; 52 | this.exclude = config.excludes; 53 | this.isLegacy = true; 54 | } else { 55 | this.schema = config.schema; 56 | this.documents = config.documents; 57 | this.include = config.include; 58 | this.exclude = config.exclude; 59 | this.isLegacy = false; 60 | } 61 | 62 | this._extensionsRegistry = extensionsRegistry; 63 | } 64 | 65 | hasExtension(name: string): boolean { 66 | return Boolean(this.extensions[name]); 67 | } 68 | 69 | extension(name: string): T { 70 | if (this.isLegacy) { 71 | const extension = this.extensions[name]; 72 | 73 | if (!extension) { 74 | throw new ExtensionMissingError(`Project ${this.name} is missing ${name} extension`); 75 | } 76 | 77 | return extension; 78 | } 79 | 80 | const extension = this._extensionsRegistry.get(name); 81 | 82 | if (!extension) { 83 | throw new ExtensionMissingError(`Project ${this.name} is missing ${name} extension`); 84 | } 85 | 86 | return { 87 | ...this.extensions[name], 88 | schema: this.schema, 89 | documents: this.documents, 90 | include: this.include, 91 | exclude: this.exclude, 92 | }; 93 | } 94 | 95 | // Get Schema 96 | 97 | async getSchema(): Promise; 98 | async getSchema(out: 'DocumentNode'): Promise; 99 | async getSchema(out: 'GraphQLSchema'): Promise; 100 | async getSchema(out: 'string'): Promise; 101 | async getSchema(out?: SchemaOutput): Promise { 102 | return this.loadSchema(this.schema, out as any); 103 | } 104 | 105 | getSchemaSync(): GraphQLSchema; 106 | getSchemaSync(out: 'DocumentNode'): DocumentNode; 107 | getSchemaSync(out: 'GraphQLSchema'): GraphQLSchema; 108 | getSchemaSync(out: 'string'): string; 109 | getSchemaSync(out?: SchemaOutput): GraphQLSchema | DocumentNode | string { 110 | return this.loadSchemaSync(this.schema, out as any); 111 | } 112 | 113 | // Get Documents 114 | 115 | async getDocuments(): Promise { 116 | if (!this.documents) { 117 | return []; 118 | } 119 | 120 | return this.loadDocuments(this.documents); 121 | } 122 | 123 | getDocumentsSync(): Source[] { 124 | if (!this.documents) { 125 | return []; 126 | } 127 | 128 | return this.loadDocumentsSync(this.documents); 129 | } 130 | 131 | // Load Schema 132 | 133 | async loadSchema(pointer: Pointer): Promise; 134 | async loadSchema(pointer: Pointer, out: 'string', options?: LoadSchemaOptions): Promise; 135 | async loadSchema(pointer: Pointer, out: 'DocumentNode', options?: LoadSchemaOptions): Promise; 136 | async loadSchema(pointer: Pointer, out: 'GraphQLSchema', options?: LoadSchemaOptions): Promise; 137 | async loadSchema( 138 | pointer: Pointer, 139 | out?: SchemaOutput, 140 | options?: LoadSchemaOptions, 141 | ): Promise { 142 | return this._extensionsRegistry.loaders.schema.loadSchema(pointer, out as any, options); 143 | } 144 | 145 | loadSchemaSync(pointer: Pointer): GraphQLSchema; 146 | loadSchemaSync(pointer: Pointer, out: 'string', options?: LoadSchemaOptions): GraphQLSchema; 147 | loadSchemaSync(pointer: Pointer, out: 'DocumentNode', options?: LoadSchemaOptions): DocumentNode; 148 | loadSchemaSync(pointer: Pointer, out: 'GraphQLSchema', options?: LoadSchemaOptions): GraphQLSchema; 149 | loadSchemaSync( 150 | pointer: Pointer, 151 | out?: SchemaOutput, 152 | options?: LoadSchemaOptions, 153 | ): GraphQLSchema | DocumentNode | string { 154 | return this._extensionsRegistry.loaders.schema.loadSchemaSync(pointer, out as any, options); 155 | } 156 | 157 | // Load Documents 158 | 159 | async loadDocuments(pointer: Pointer, options?: LoadTypedefsOptions): Promise { 160 | if (!pointer) { 161 | return []; 162 | } 163 | 164 | return this._extensionsRegistry.loaders.documents.loadDocuments(pointer, options); 165 | } 166 | 167 | loadDocumentsSync(pointer: Pointer, options?: LoadTypedefsOptions): Source[] { 168 | if (!pointer) { 169 | return []; 170 | } 171 | 172 | return this._extensionsRegistry.loaders.documents.loadDocumentsSync(pointer, options); 173 | } 174 | 175 | // Rest 176 | 177 | match(filepath: string): boolean { 178 | const isSchemaOrDocument = [this.schema, this.documents].some((pointer) => match(filepath, this.dirpath, pointer)); 179 | 180 | if (isSchemaOrDocument) { 181 | return true; 182 | } 183 | 184 | const isExcluded = this.exclude ? match(filepath, this.dirpath, this.exclude) : false; 185 | 186 | if (isExcluded) { 187 | return false; 188 | } 189 | 190 | return this.include ? match(filepath, this.dirpath, this.include) : false; 191 | } 192 | } 193 | 194 | function isSDLSchemaLike(schema: string): boolean { 195 | return schema.includes('\n'); 196 | } 197 | 198 | // XXX: it works but uses Node.js - expose normalization of file and dir paths in config 199 | function match(filepath: string, dirpath: string, pointer?: Pointer): boolean { 200 | if (!pointer) { 201 | return false; 202 | } 203 | 204 | if (Array.isArray(pointer)) { 205 | return pointer.some((p) => match(filepath, dirpath, p)); 206 | } 207 | 208 | if (typeof pointer === 'string') { 209 | if (isSDLSchemaLike(pointer)) { 210 | return false; 211 | } 212 | 213 | const normalizedFilepath = normalize(isAbsolute(filepath) ? relative(dirpath, filepath) : filepath) 214 | .split('\\') 215 | .join('/'); 216 | return minimatch(normalizedFilepath, normalize(pointer).split('\\').join('/'), { dot: true }); 217 | } 218 | 219 | if (typeof pointer === 'object') { 220 | return match(filepath, dirpath, Object.keys(pointer)[0]); 221 | } 222 | 223 | return false; 224 | } 225 | -------------------------------------------------------------------------------- /website/src/app/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /test/config.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, buildASTSchema } from 'graphql'; 2 | import path from 'path'; 3 | import { TempDir } from './utils/temp-dir'; 4 | import { runTests } from './utils/runner'; 5 | import { loadConfig, loadConfigSync, ConfigNotFoundError, GraphQLConfig } from 'graphql-config'; 6 | 7 | const temp = new TempDir(); 8 | 9 | beforeEach(() => { 10 | temp.clean(); 11 | }); 12 | 13 | beforeAll(() => { 14 | process.env.SCHEMA = './env.graphql'; 15 | }); 16 | 17 | afterAll(() => { 18 | temp.deleteTempDir(); 19 | process.env.SCHEMA = undefined; 20 | }); 21 | 22 | runTests({ async: loadConfig, sync: loadConfigSync })((load, mode) => { 23 | const testSDL = /* GraphQL */ ` 24 | type Query { 25 | foo: String 26 | } 27 | `; 28 | const testYAML = 'schema: schema.graphql'; 29 | const schemaFilename = 'schema.graphql'; 30 | 31 | describe('loaders', () => { 32 | test('load a single graphql file', async () => { 33 | temp.createFile('.graphqlrc', testYAML); 34 | temp.createFile(schemaFilename, testSDL); 35 | 36 | const config = await load({ rootDir: temp.dir }); 37 | 38 | const schema = mode === 'async' ? await config.getDefault().getSchema() : config.getDefault().getSchemaSync(); 39 | const query = schema.getQueryType(); 40 | const fields = Object.keys(query.getFields()); 41 | 42 | expect(query).toBeDefined(); 43 | expect(fields).toContainEqual('foo'); 44 | }); 45 | 46 | test('load a single graphql file as string', async () => { 47 | temp.createFile('.graphqlrc', testYAML); 48 | temp.createFile(schemaFilename, testSDL); 49 | 50 | const config = await load({ rootDir: temp.dir }); 51 | 52 | const schema = buildSchema( 53 | mode === 'async' ? await config.getDefault().getSchema('string') : config.getDefault().getSchemaSync('string'), 54 | ); 55 | const query = schema.getQueryType(); 56 | const fields = Object.keys(query.getFields()); 57 | 58 | expect(query).toBeDefined(); 59 | expect(fields).toContainEqual('foo'); 60 | }); 61 | 62 | test('load a single graphql file as DocumentNode', async () => { 63 | temp.createFile('.graphqlrc', testYAML); 64 | temp.createFile(schemaFilename, testSDL); 65 | 66 | const config = await load({ rootDir: temp.dir }); 67 | 68 | const schema = buildASTSchema( 69 | mode === 'async' 70 | ? await config.getDefault().getSchema('DocumentNode') 71 | : config.getDefault().getSchemaSync('DocumentNode'), 72 | ); 73 | const query = schema.getQueryType(); 74 | const fields = Object.keys(query.getFields()); 75 | 76 | expect(query).toBeDefined(); 77 | expect(fields).toContainEqual('foo'); 78 | }); 79 | }); 80 | 81 | describe('loading from supported config files', () => { 82 | const moduleName = 'graphql'; 83 | 84 | const esmConfigTs = `export default { schema: '${schemaFilename}' } satisfies any`; 85 | const esmConfig = `export default { schema: '${schemaFilename}' }`; 86 | const cjsConfigTs = `module.exports = { schema: '${schemaFilename}' } satisfies any`; 87 | const cjsConfig = `module.exports = { schema: '${schemaFilename}' }`; 88 | 89 | const yamlConfig = `schema: '${schemaFilename}'`; 90 | const tomlConfig = `schema = "${schemaFilename}"`; 91 | const jsonConfig = `{"schema": "${schemaFilename}"}`; 92 | const packageJsonConfig = `{"${moduleName}": {"schema": "${schemaFilename}"}}`; 93 | 94 | const typeModule = '{"type":"module"}'; 95 | const typeCommonjs = '{"type":"commonjs"}'; 96 | 97 | const configFiles: [string, string, packageJson?: string][] = [ 98 | // #.config files 99 | [`${moduleName}.config.ts`, esmConfigTs], 100 | [`${moduleName}.config.js`, esmConfig, typeModule], 101 | [`${moduleName}.config.js`, cjsConfig, typeCommonjs], 102 | [`${moduleName}.config.js`, cjsConfig], 103 | [`${moduleName}.config.cts`, cjsConfigTs], 104 | [`${moduleName}.config.cjs`, cjsConfig], 105 | [`${moduleName}.config.mts`, esmConfigTs], 106 | [`${moduleName}.config.mjs`, esmConfig], 107 | 108 | [`${moduleName}.config.json`, jsonConfig], 109 | [`${moduleName}.config.yaml`, yamlConfig], 110 | [`${moduleName}.config.yml`, yamlConfig], 111 | [`${moduleName}.config.toml`, tomlConfig], 112 | // .#rc files 113 | [`.${moduleName}rc`, yamlConfig], 114 | [`.${moduleName}rc.ts`, esmConfigTs], 115 | [`.${moduleName}rc.js`, esmConfig, typeModule], 116 | [`.${moduleName}rc.js`, esmConfig, typeCommonjs], 117 | [`.${moduleName}rc.js`, cjsConfig], 118 | [`.${moduleName}rc.cts`, cjsConfigTs], 119 | [`.${moduleName}rc.cjs`, cjsConfig], 120 | [`.${moduleName}rc.mts`, esmConfigTs], 121 | [`.${moduleName}rc.mjs`, esmConfig], 122 | 123 | [`.${moduleName}rc.json`, jsonConfig], 124 | [`.${moduleName}rc.yml`, yamlConfig], 125 | [`.${moduleName}rc.yaml`, yamlConfig], 126 | [`.${moduleName}rc.toml`, tomlConfig], 127 | // other files 128 | ['package.json', packageJsonConfig], 129 | ]; 130 | 131 | if (mode === 'async') { 132 | const topAwaitConfigTs = `await Promise.resolve(); export default { schema: '${schemaFilename}' } satisfies any`; 133 | const topAwaitConfig = `await Promise.resolve(); export default { schema: '${schemaFilename}' }`; 134 | 135 | configFiles.push( 136 | // #.config files 137 | [`${moduleName}.config.ts`, topAwaitConfigTs, typeModule], 138 | [`${moduleName}.config.js`, topAwaitConfig, typeModule], 139 | [`${moduleName}.config.mts`, topAwaitConfigTs], 140 | [`${moduleName}.config.mjs`, topAwaitConfig], 141 | 142 | // .#rc files 143 | [`.${moduleName}rc.ts`, topAwaitConfigTs, typeModule], 144 | [`.${moduleName}rc.js`, topAwaitConfig, typeModule], 145 | [`.${moduleName}rc.mts`, topAwaitConfigTs], 146 | [`.${moduleName}rc.mjs`, topAwaitConfig], 147 | ); 148 | } 149 | 150 | beforeEach(() => { 151 | temp.clean(); 152 | temp.createFile(schemaFilename, testSDL); 153 | }); 154 | 155 | test.each(configFiles)('load config from "%s"', async (name, content, packageJson) => { 156 | temp.createFile(name, content); 157 | if (packageJson) { 158 | temp.createFile('package.json', packageJson); 159 | } 160 | 161 | const config = await load({ rootDir: temp.dir }); 162 | 163 | const loadedFileName = path.basename(config.filepath); 164 | const loadedSchema = config.getDefault().schema; 165 | 166 | expect(config).toBeDefined(); 167 | expect(loadedFileName).toEqual(name); 168 | expect(loadedSchema).toEqual(schemaFilename); 169 | }); 170 | }); 171 | 172 | describe('environment variables', () => { 173 | test('not defined but with a default value', async () => { 174 | temp.createFile('.graphqlrc', 'schema: ${FOO:./schema.graphql}'); 175 | 176 | const config = await load({ rootDir: temp.dir }); 177 | expect(config.getDefault().schema).toEqual('./schema.graphql'); 178 | }); 179 | 180 | test('not defined but with a default value inside quotation marks', async () => { 181 | const url = 'http://localhost:9000'; 182 | temp.createFile('.graphqlrc', `schema: \${FOO:"${url}"}`); 183 | 184 | const config = await load({ rootDir: temp.dir }); 185 | expect(config.getDefault().schema).toEqual(url); 186 | }); 187 | 188 | test('not defined but with a default value inside quotation marks', async () => { 189 | const url = 'http://localhost:9000'; 190 | temp.createFile('.graphqlrc', `schema: \${FOO:${url}}`); 191 | 192 | const config = await load({ rootDir: temp.dir }); 193 | expect(config.getDefault().schema).toEqual(url); 194 | }); 195 | 196 | test('defined and with a default value', async () => { 197 | temp.createFile('.graphqlrc', 'schema: ${SCHEMA:./schema.graphql}'); 198 | 199 | const config = await load({ rootDir: temp.dir }); 200 | expect(config.getDefault().schema).toEqual('./env.graphql'); 201 | }); 202 | }); 203 | 204 | describe('project matching by file path', () => { 205 | test('basic check', async () => { 206 | temp.createFile( 207 | '.graphqlrc', 208 | ` 209 | projects: 210 | foo: 211 | schema: ./foo.graphql 212 | bar: 213 | schema: 214 | - ./bar.graphql: 215 | noop: true 216 | baz: 217 | schema: ./documents/**/*.graphql 218 | 219 | qux: 220 | schema: 221 | - ./schemas/foo.graphql 222 | - ./schemas/bar.graphql`, 223 | ); 224 | 225 | const config = await load({ rootDir: temp.dir }); 226 | 227 | expect(config.getProjectForFile('./foo.graphql').name).toBe('foo'); 228 | expect(config.getProjectForFile(path.resolve(temp.dir, './foo.graphql')).name).toBe('foo'); 229 | expect(config.getProjectForFile('./bar.graphql').name).toBe('bar'); 230 | expect(config.getProjectForFile(path.resolve(temp.dir, './bar.graphql')).name).toBe('bar'); 231 | expect(config.getProjectForFile('./schemas/foo.graphql').name).toBe('qux'); 232 | expect(config.getProjectForFile('./schemas/bar.graphql').name).toBe('qux'); 233 | expect(config.getProjectForFile('./documents/baz.graphql').name).toBe('baz'); 234 | }); 235 | 236 | test('consider include', async () => { 237 | temp.createFile( 238 | '.graphqlrc', 239 | ` 240 | projects: 241 | foo: 242 | schema: ./foo.graphql 243 | include: ./foo/*.ts 244 | bar: 245 | schema: ./bar.graphql 246 | documents: ./documents/**/*.graphql 247 | `, 248 | ); 249 | 250 | const config = await load({ rootDir: temp.dir }); 251 | 252 | expect(config.getProjectForFile('./foo/component.ts').name).toBe('foo'); 253 | expect(config.getProjectForFile('./documents/barbar.graphql').name).toBe('bar'); 254 | }); 255 | 256 | test('consider exclude', async () => { 257 | temp.createFile( 258 | '.graphqlrc', 259 | ` 260 | projects: 261 | foo: 262 | schema: ./foo.graphql 263 | include: ./foo/*.ts 264 | exclude: ./foo/ignored/** 265 | bar: 266 | schema: ./bar.graphql 267 | documents: ./documents/**/*.graphql 268 | `, 269 | ); 270 | 271 | const config = await load({ rootDir: temp.dir }); 272 | 273 | expect(config.getProjectForFile('./foo/component.ts').name).toBe('foo'); 274 | // should point to a next project that includes the file 275 | expect(config.getProjectForFile('./foo/ignored/component.ts').name).toBe('bar'); 276 | }); 277 | 278 | test('customizable config name', async () => { 279 | temp.createFile(schemaFilename, testSDL); 280 | temp.createFile('foo.config.js', `module.exports = { schema: '${schemaFilename}' }`); 281 | 282 | try { 283 | await load({ rootDir: temp.dir }); 284 | 285 | throw new Error('Should not be here'); 286 | } catch (error) { 287 | expect(error).toBeInstanceOf(ConfigNotFoundError); 288 | } 289 | 290 | const config = await load({ 291 | rootDir: temp.dir, 292 | configName: 'foo', 293 | }); 294 | 295 | expect(config.getDefault().schema).toEqual(schemaFilename); 296 | }); 297 | }); 298 | }); 299 | 300 | describe('GraphQLConfig', () => { 301 | const MINIMATCH_MAX_LENGTH = 65_536; 302 | 303 | // https://github.com/dimaMachina/graphql-eslint/issues/2046 304 | it(`should not throw \`pattern is too long\` from minimatch dependency when SDL schema contain more than ${MINIMATCH_MAX_LENGTH} characters`, async () => { 305 | const schema = Array.from({ length: 2_150 }, (_, i) => `type Query${i} { foo: String }`).join('\n'); 306 | const graphQLConfig = new GraphQLConfig({ config: { schema }, filepath: '' }, []); 307 | expect(schema.length).toBeGreaterThan(MINIMATCH_MAX_LENGTH); 308 | expect(() => graphQLConfig.getProjectForFile('foo')).not.toThrow(); 309 | }); 310 | }); 311 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## 5.1.5 4 | 5 | ### Patch Changes 6 | 7 | - [#1727](https://github.com/graphql-hive/graphql-config/pull/1727) [`4f2bda9`](https://github.com/graphql-hive/graphql-config/commit/4f2bda98d8b00b0f015c260885202fd53550da9f) Thanks [@eddeee888](https://github.com/eddeee888)! - Revert minimatch and cosmiconfig versions 8 | 9 | ## 5.1.4 10 | 11 | ### Patch Changes 12 | 13 | - [#1631](https://github.com/graphql-hive/graphql-config/pull/1631) [`790cfc1`](https://github.com/graphql-hive/graphql-config/commit/790cfc1df60ad2738b0c0ed71436c4bbbfbc248c) Thanks [@aaronadamsCA](https://github.com/aaronadamsCA)! - support top-level await 14 | 15 | - [#1499](https://github.com/graphql-hive/graphql-config/pull/1499) [`7f80597`](https://github.com/graphql-hive/graphql-config/commit/7f80597838415fe291a92d56a5ce552e91407a9e) Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 16 | 17 | - Updated dependency [`minimatch@^10.0.0` ↗︎](https://www.npmjs.com/package/minimatch/v/10.0.0) (from `^9.0.5`, in `dependencies`) 18 | 19 | - [#1574](https://github.com/graphql-hive/graphql-config/pull/1574) [`c8efc31`](https://github.com/graphql-hive/graphql-config/commit/c8efc31f2697491b8b7dadc55b176949a09592b9) Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 20 | 21 | - Updated dependency [`cosmiconfig@^9.0.0` ↗︎](https://www.npmjs.com/package/cosmiconfig/v/9.0.0) (from `^8.1.0`, in `dependencies`) 22 | 23 | - [#1725](https://github.com/graphql-hive/graphql-config/pull/1725) [`3f9ebc7`](https://github.com/graphql-hive/graphql-config/commit/3f9ebc702768dcbe5829940a78e618dc3ce0266d) Thanks [@eddeee888](https://github.com/eddeee888)! - Bump @graphql-tools/load to ^8.1.0 to support better error handling 24 | 25 | ## 5.1.3 26 | 27 | ### Patch Changes 28 | 29 | - [#1572](https://github.com/kamilkisiela/graphql-config/pull/1572) [`d95462c`](https://github.com/kamilkisiela/graphql-config/commit/d95462c4ebf57cac2c2af7e8be97d5bb504fb591) Thanks [@dimaMachina](https://github.com/dimaMachina)! - revert cosmiconfig update to v8 30 | 31 | ## 5.1.2 32 | 33 | ### Patch Changes 34 | 35 | - [#1489](https://github.com/kamilkisiela/graphql-config/pull/1489) [`9a89093`](https://github.com/kamilkisiela/graphql-config/commit/9a8909375f72c6040cd5441fb88bd86035159719) Thanks [@that1matt](https://github.com/that1matt)! - Change minimatch to version 9 36 | 37 | ## 5.1.1 38 | 39 | ### Patch Changes 40 | 41 | - [#1481](https://github.com/kamilkisiela/graphql-config/pull/1481) [`5d00c94`](https://github.com/kamilkisiela/graphql-config/commit/5d00c94fafb220a9e101c192bffcce70dc194b75) Thanks [@renovate](https://github.com/apps/renovate)! - Update minimatch 42 | 43 | ## 5.1.0 44 | 45 | ### Minor Changes 46 | 47 | - [#1459](https://github.com/kamilkisiela/graphql-config/pull/1459) [`5eca929`](https://github.com/kamilkisiela/graphql-config/commit/5eca92966fece546d39db39e647158a1081cee46) Thanks [@dimaMachina](https://github.com/dimaMachina)! - - fix loading esm js config 48 | 49 | - add support of `*.mjs` configs 50 | 51 | ### Patch Changes 52 | 53 | - [#1418](https://github.com/kamilkisiela/graphql-config/pull/1418) [`658f984`](https://github.com/kamilkisiela/graphql-config/commit/658f98427d620a9cb8ca6c18e415b75b087794b8) Thanks [@dimaMachina](https://github.com/dimaMachina)! - should not throw `pattern is too long` from minimatch dependency when SDL schema contain more than 65536 characters 54 | 55 | ## 5.0.3 56 | 57 | ### Patch Changes 58 | 59 | - [#1406](https://github.com/kamilkisiela/graphql-config/pull/1406) [`69afec4`](https://github.com/kamilkisiela/graphql-config/commit/69afec4bbabcee453c14737257c43f8b1919f532) Thanks [@B2o5T](https://github.com/B2o5T)! - fix `SchemaPointer` type, allow both URLs with headers and local type definitions 60 | 61 | ## 5.0.2 62 | 63 | ### Patch Changes 64 | 65 | - [#1370](https://github.com/kamilkisiela/graphql-config/pull/1370) [`156e7c2`](https://github.com/kamilkisiela/graphql-config/commit/156e7c2cc128e4ec19f8e207bc040dd599132e38) Thanks [@gilgardosh](https://github.com/gilgardosh)! - Bump bob-the-bundler 66 | 67 | ## 5.0.1 68 | 69 | ### Patch Changes 70 | 71 | - [#1359](https://github.com/kamilkisiela/graphql-config/pull/1359) [`18bfca8`](https://github.com/kamilkisiela/graphql-config/commit/18bfca88bf46455582e28b1c8d2ccf0c20f0dc75) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Fix esm compatibility 72 | 73 | ## 5.0.0 74 | 75 | ### Major Changes 76 | 77 | - [#1348](https://github.com/kamilkisiela/graphql-config/pull/1348) [`42ffb2e`](https://github.com/kamilkisiela/graphql-config/commit/42ffb2e82d9d7a170ae1a9b9f52cdcd396046d80) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Drop support for Node.js 14. Require Node.js `>= 16` 78 | 79 | ### Patch Changes 80 | 81 | - [#1294](https://github.com/kamilkisiela/graphql-config/pull/1294) [`1d11dbd`](https://github.com/kamilkisiela/graphql-config/commit/1d11dbd25e581cb6c0e216c3e2917ab7f47d6847) Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 82 | 83 | - Updated dependency [`jiti@1.18.2` ↗︎](https://www.npmjs.com/package/jiti/v/1.18.2) (from `1.17.1`, in `dependencies`) 84 | 85 | - [#1348](https://github.com/kamilkisiela/graphql-config/pull/1348) [`42ffb2e`](https://github.com/kamilkisiela/graphql-config/commit/42ffb2e82d9d7a170ae1a9b9f52cdcd396046d80) Thanks [@n1ru4l](https://github.com/n1ru4l)! - dependencies updates: 86 | 87 | - Updated dependency [`@graphql-tools/graphql-file-loader@^8.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/graphql-file-loader/v/8.0.0) (from `^7.3.7`, in `dependencies`) 88 | - Updated dependency [`@graphql-tools/json-file-loader@^8.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/json-file-loader/v/8.0.0) (from `^7.3.7`, in `dependencies`) 89 | - Updated dependency [`@graphql-tools/load@^8.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/load/v/8.0.0) (from `^7.5.5`, in `dependencies`) 90 | - Updated dependency [`@graphql-tools/merge@^9.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/merge/v/9.0.0) (from `^8.2.6`, in `dependencies`) 91 | - Updated dependency [`@graphql-tools/url-loader@^8.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/url-loader/v/8.0.0) (from `^7.9.7`, in `dependencies`) 92 | - Updated dependency [`@graphql-tools/utils@^10.0.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/10.0.0) (from `^9.0.0`, in `dependencies`) 93 | 94 | - [#1358](https://github.com/kamilkisiela/graphql-config/pull/1358) [`d6ead74`](https://github.com/kamilkisiela/graphql-config/commit/d6ead7426d3e9a7e2309b1939bfa66ae45e9e7d9) Thanks [@n1ru4l](https://github.com/n1ru4l)! - dependencies updates: 95 | 96 | - Updated dependency [`cosmiconfig@^8.1.0` ↗︎](https://www.npmjs.com/package/cosmiconfig/v/8.1.0) (from `8.1.0`, in `dependencies`) 97 | - Updated dependency [`jiti@^1.18.2` ↗︎](https://www.npmjs.com/package/jiti/v/1.18.2) (from `1.18.2`, in `dependencies`) 98 | - Updated dependency [`minimatch@^4.2.3` ↗︎](https://www.npmjs.com/package/minimatch/v/4.2.3) (from `4.2.3`, in `dependencies`) 99 | - Updated dependency [`string-env-interpolation@^1.0.1` ↗︎](https://www.npmjs.com/package/string-env-interpolation/v/1.0.1) (from `1.0.1`, in `dependencies`) 100 | 101 | ## 4.5.0 102 | 103 | ### Minor Changes 104 | 105 | - [`9e4f453`](https://github.com/kamilkisiela/graphql-config/commit/9e4f453f463dbf39228de39c00ccc4b7014b9614) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support ESM and .mts/.cts config extensions 106 | 107 | ## 4.4.1 108 | 109 | ### Patch Changes 110 | 111 | - [#1245](https://github.com/kamilkisiela/graphql-config/pull/1245) [`bad5090`](https://github.com/kamilkisiela/graphql-config/commit/bad509048c971872224fe8eaddda84dd948e57c3) Thanks [@B2o5T](https://github.com/B2o5T)! - fix `peerDependenciesMeta` was not included in `package.json` 112 | 113 | ## 4.4.0 114 | 115 | ### Minor Changes 116 | 117 | - [#1189](https://github.com/kamilkisiela/graphql-config/pull/1189) [`ab2ad6d`](https://github.com/kamilkisiela/graphql-config/commit/ab2ad6d558e55d5d9b52609c68d0404026e34f1e) Thanks [@B2o5T](https://github.com/B2o5T)! - add `cosmiconfig-toml-loader` to `peerDependenciesMeta` 118 | 119 | - [#1171](https://github.com/kamilkisiela/graphql-config/pull/1171) [`b52dc1b`](https://github.com/kamilkisiela/graphql-config/commit/b52dc1b7ed46c056dea39396ffbb89a400cee7d6) Thanks [@B2o5T](https://github.com/B2o5T)! - move `cosmiconfig-typescript-loader` in `peerDependencyMeta` 120 | 121 | ## 4.3.6 122 | 123 | ### Patch Changes 124 | 125 | - [#1149](https://github.com/kamilkisiela/graphql-config/pull/1149) [`a12f394`](https://github.com/kamilkisiela/graphql-config/commit/a12f3945b3da70a9c10e0436785f96958202912e) Thanks [@charlypoly](https://github.com/charlypoly)! - conflict with codegen also using TypeScriptLoader(), causing a double ts-node register. 126 | 127 | ## 4.3.5 128 | 129 | ### Patch Changes 130 | 131 | - [#1126](https://github.com/kamilkisiela/graphql-config/pull/1126) [`cc781c4`](https://github.com/kamilkisiela/graphql-config/commit/cc781c4cf3bd056a75081108e1b13efd1b3d29ed) Thanks [@n1ru4l](https://github.com/n1ru4l)! - dependencies updates: 132 | 133 | - Updated dependency [`cosmiconfig-typescript-loader@^4.0.0` ↗︎](https://www.npmjs.com/package/cosmiconfig-typescript-loader/v/null) (from `^3.1.0`, in `dependencies`) 134 | 135 | ## 4.3.4 136 | 137 | ### Patch Changes 138 | 139 | - [#1103](https://github.com/kamilkisiela/graphql-config/pull/1103) [`2c568f1`](https://github.com/kamilkisiela/graphql-config/commit/2c568f1ee2d45bc46613b86b12fcfab82b1393aa) Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates: 140 | 141 | - Added dependency [`tslib@^2.4.0` ↗︎](https://www.npmjs.com/package/tslib/v/null) (to `dependencies`) 142 | 143 | * [#1103](https://github.com/kamilkisiela/graphql-config/pull/1103) [`2c568f1`](https://github.com/kamilkisiela/graphql-config/commit/2c568f1ee2d45bc46613b86b12fcfab82b1393aa) Thanks [@renovate](https://github.com/apps/renovate)! - Proper ESM/CJS support on Node.js 144 | 145 | ## 4.3.3 146 | 147 | ### Patch Changes 148 | 149 | - cd7747e: bump `cosmiconfig-typescript-loader` to resolve errors with esm loading 150 | 151 | ## 4.3.2 152 | 153 | ### Patch Changes 154 | 155 | - f74d648: fix: change to maintained version of `cosmiconfig-typescript-loader` 156 | 157 | ## 4.3.1 158 | 159 | ### Patch Changes 160 | 161 | - 44eec8d: Add workaround for default import of typescript config loader to fix ESM support 162 | 163 | ## 4.3.0 164 | 165 | ### Minor Changes 166 | 167 | - aaccd04: feat: improve types to fix JSON schema when schema is passed like object with headers 168 | 169 | ### Patch Changes 170 | 171 | - 18d07fd: fix: rollback `GraphQLConfig.projects` to empty object instead `Object.create(null)` 172 | 173 | ## 4.2.0 174 | 175 | ### Minor Changes 176 | 177 | - 0636e9a: feat: support `graphql.config.cjs` config 178 | - 55f078a: feat: update `graphql-tools` packages 179 | 180 | ### Patch Changes 181 | 182 | - fix: update `minimatch` dependency 183 | 184 | Thanks to @bfanger for his first contribution 0636e9a 185 | 186 | ### vNEXT 187 | 188 | ### v4.0.2 189 | 190 | - Update range of `@graphql-tools/merge` dependency to include v7 and v8 191 | 192 | ### v4.0.1 193 | 194 | - Updated dependencies of `graphql-tools` to latest, to address issues related to documents loading. 195 | 196 | ### v4.0.0 197 | 198 | ‼️ ‼️ ‼️ BREAKING CHANGE ‼️ ‼️ ‼️ 199 | 200 | Dropped Node 10 support, due to the need to support ESM in this package. 201 | 202 | ‼️ ‼️ ‼️ BREAKING CHANGE ‼️ ‼️ ‼️ 203 | 204 | The signature of `Loader` has been changed in `graphql-tools`, to allow more flexibility. 205 | 206 | If you are using `graphql-config` with `extensions`, then the `Extension` you are using needs to adjust to the new return value of `Loader` signature that returns `Source[] | null` instead of `Source`. (see: https://github.com/kamilkisiela/graphql-config/issues/716) 207 | 208 | Other changes: 209 | 210 | - ESM Support 211 | - Update dependencies of `graphql-tools`. 212 | 213 | ### v3.4.0 214 | 215 | > Note: A breaking chnage snuk into that version, please see v4. 216 | 217 | - Update dependencies of `graphql-tools`. 218 | 219 | ### v3.3.0 220 | 221 | - Add support for loading the config from package.json [#693](https://github.com/kamilkisiela/graphql-config/pull/693) by [@ionut-botizan](https://github.com/ionut-botizan) 222 | 223 | ### v3.2.0 224 | 225 | - Allow custom options for loadSchema [#593](https://github.com/kamilkisiela/graphql-config/pull/593) 226 | 227 | ### v3.1.0 228 | 229 | - TOML and TypeScript loaders [#595](https://github.com/kamilkisiela/graphql-config/pull/595) by [@acao](https://github.com/acao) 230 | - Add ability to override default loaders [#583](https://github.com/kamilkisiela/graphql-config/pull/583) by [@danielrearden](https://github.com/danielrearden) 231 | 232 | ### v3.0.2 233 | 234 | - Fix missing types [#542](https://github.com/kamilkisiela/graphql-config/issues/542) 235 | 236 | ### v3.0.1 237 | 238 | - use GraphQL Toolkit v0.10.6 239 | 240 | ### v3.0.0 241 | 242 | > Read the [Migration chapter](https://graphql-config.com/migration) 243 | 244 | - Support GraphQL v15 245 | - Support CommonJS and ES Modules 246 | - Support environment variables with default values 247 | - Match a file with a GraphQL Project 248 | - JSON Schema 249 | - [New Extensions system](https://graphql-config.com/extensions) with [Loaders](https://graphql-config.com/loaders) 250 | - `includes` and `excludes` becomes `include` and `exlude` 251 | - New field `documents` - defines GraphQL Operations and Fragments 252 | - Broader spectrum of [config file names](https://graphql-config.com/usage#config-search-places) 253 | - Support [custom config name](https://graphql-config.com/load-config#configname) 254 | - Synchonous version 255 | - Support legacy [#437](https://github.com/kamilkisiela/graphql-config/pull/437) 256 | - Extensions capable of modifying GraphQL Schemas [#463](https://github.com/kamilkisiela/graphql-config/pull/463) 257 | 258 | ### Prior to v3 259 | 260 | Changes: https://github.com/kamilkisiela/graphql-config/releases 261 | -------------------------------------------------------------------------------- /website/public/configuring.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 10 | 12 | 14 | 15 | 17 | 19 | 20 | 21 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 47 | 50 | 52 | 54 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 70 | 71 | 73 | 74 | 76 | 78 | 79 | 81 | 83 | 85 | 87 | 89 | 90 | 92 | 93 | 95 | 96 | 98 | 100 | 102 | 104 | 106 | 108 | 110 | 112 | 114 | 116 | 118 | 120 | 122 | 123 | 124 | 125 | 127 | 128 | 130 | 131 | 133 | 135 | 137 | 139 | 141 | 143 | 145 | 147 | 149 | 150 | 151 | --------------------------------------------------------------------------------