├── .changeset ├── @graphql-inspector_action-2636-dependencies.md ├── @graphql-inspector_action-2767-dependencies.md ├── config.json └── lazy-lemons-vanish.md ├── .editorconfig ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature-request.md ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json └── workflows │ ├── actions.yml │ ├── ci.yml │ ├── docker.yml │ ├── pr.yml │ ├── release.yml │ └── website.yml ├── .gitignore ├── .node-version ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .vscode └── settings.json ├── .whitesource ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── action ├── index.js └── package.json ├── assets ├── coverage.jpg ├── diff.jpg ├── github.jpg ├── similar.jpg └── validate.jpg ├── example ├── coverage.json ├── documents │ ├── depth-post.graphql │ └── post.graphql ├── package.json ├── rules │ └── custom-rule.js └── schemas │ ├── deep.graphql │ ├── new-valid.graphql │ ├── new.graphql │ ├── new.json │ ├── schema.graphql │ ├── schema.js │ ├── schema.json │ └── similar.graphql ├── integration_tests ├── 1985 │ ├── package.json │ └── schema.js ├── 1991 │ ├── schema.graphql │ └── two-operations.js ├── 2001 │ ├── operations │ │ ├── companies.graphql │ │ └── hotels.graphql │ └── schema.graphql ├── 2027 │ ├── schema-after.graphql │ ├── schema-before.graphql │ └── unused-hotels.js ├── 2088 │ ├── new.graphql │ └── old.graphql ├── 2108 │ ├── excluded │ │ └── schema.graphql │ └── included │ │ └── schema.graphql └── 2239 │ ├── newSchema.graphql │ └── oldSchema.graphql ├── package.json ├── packages ├── action │ ├── CHANGELOG.md │ ├── LICENSE │ ├── __tests__ │ │ └── run.test.ts │ ├── helpers │ │ ├── check-runs.ts │ │ ├── config.ts │ │ ├── diagnostics.ts │ │ ├── diff.ts │ │ ├── errors.ts │ │ ├── loaders.ts │ │ ├── location.ts │ │ ├── logger.ts │ │ ├── notifications.ts │ │ ├── schema.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── package.json │ └── src │ │ ├── action.ts │ │ ├── checks.ts │ │ ├── files.ts │ │ ├── git.ts │ │ ├── index.ts │ │ ├── run.ts │ │ ├── types.ts │ │ └── utils.ts ├── ci │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ └── index.ts ├── cli │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── demo.gif │ ├── demo.yml │ ├── package.json │ └── src │ │ └── index.ts ├── commands │ ├── audit │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── commands │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── coverage │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── diff │ │ ├── CHANGELOG.md │ │ ├── __tests__ │ │ │ ├── assets │ │ │ │ ├── on-complete.cjs │ │ │ │ └── rule.cjs │ │ │ └── diff-command.test.ts │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── docs │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── introspect │ │ ├── CHANGELOG.md │ │ ├── __tests__ │ │ │ └── introspect-command.test.ts │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── serve │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ ├── fake.ts │ │ │ └── index.ts │ ├── similar │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ └── validate │ │ ├── CHANGELOG.md │ │ ├── __tests__ │ │ └── validate-command.test.ts │ │ ├── package.json │ │ └── src │ │ └── index.ts ├── config │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ └── index.ts ├── core │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __tests__ │ │ ├── coverage │ │ │ └── coverage.test.ts │ │ ├── diff │ │ │ ├── argument.test.ts │ │ │ ├── directive-usage.test.ts │ │ │ ├── directive.test.ts │ │ │ ├── enum.test.ts │ │ │ ├── input.test.ts │ │ │ ├── interface.test.ts │ │ │ ├── object.test.ts │ │ │ ├── rules │ │ │ │ ├── consider-usage.test.ts │ │ │ │ ├── safe-unreachable.test.ts │ │ │ │ └── suppress-removal-of-deprecated-fields.test.ts │ │ │ ├── schema.test.ts │ │ │ └── union.test.ts │ │ ├── utils │ │ │ ├── compare.test.ts │ │ │ └── string.test.ts │ │ ├── validate.test.ts │ │ └── validate │ │ │ ├── apollo.test.ts │ │ │ ├── aws.test.ts │ │ │ ├── query-depth.test.ts │ │ │ └── token-count.test.ts │ ├── package.json │ ├── src │ │ ├── ast │ │ │ └── document.ts │ │ ├── coverage │ │ │ ├── index.ts │ │ │ └── output │ │ │ │ └── json.ts │ │ ├── diff │ │ │ ├── argument.ts │ │ │ ├── changes │ │ │ │ ├── argument.ts │ │ │ │ ├── change.ts │ │ │ │ ├── directive-usage.ts │ │ │ │ ├── directive.ts │ │ │ │ ├── enum.ts │ │ │ │ ├── field.ts │ │ │ │ ├── input.ts │ │ │ │ ├── object.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── type.ts │ │ │ │ └── union.ts │ │ │ ├── directive.ts │ │ │ ├── enum.ts │ │ │ ├── field.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── interface.ts │ │ │ ├── object.ts │ │ │ ├── onComplete │ │ │ │ └── types.ts │ │ │ ├── rules │ │ │ │ ├── config.ts │ │ │ │ ├── consider-usage.ts │ │ │ │ ├── dangerous-breaking.ts │ │ │ │ ├── ignore-description-changes.ts │ │ │ │ ├── ignore-usage-directives.ts │ │ │ │ ├── index.ts │ │ │ │ ├── safe-unreachable.ts │ │ │ │ ├── suppress-removal-of-deprecated-field.ts │ │ │ │ └── types.ts │ │ │ ├── scalar.ts │ │ │ ├── schema.ts │ │ │ └── union.ts │ │ ├── index.ts │ │ ├── similar │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── apollo.ts │ │ │ ├── compare.ts │ │ │ ├── graphql.ts │ │ │ ├── is-deprecated.ts │ │ │ ├── path.ts │ │ │ └── string.ts │ │ └── validate │ │ │ ├── alias-count.ts │ │ │ ├── complexity.ts │ │ │ ├── directive-count.ts │ │ │ ├── index.ts │ │ │ ├── query-depth.ts │ │ │ └── token-count.ts │ └── utils │ │ └── testing.ts ├── loaders │ ├── code │ │ ├── CHANGELOG.md │ │ ├── __tests__ │ │ │ ├── assets │ │ │ │ └── bar.ts │ │ │ └── code.test.ts │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── git │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── github │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── graphql │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── json │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ ├── loaders │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ └── src │ │ │ └── index.ts │ └── url │ │ ├── CHANGELOG.md │ │ ├── __tests__ │ │ └── introspection.test.ts │ │ ├── package.json │ │ └── src │ │ └── index.ts ├── logger │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── typings.d.ts └── testing │ └── src │ ├── index.ts │ ├── setup-file.ts │ └── typings.d.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.cjs ├── renovate.json ├── tsconfig.json ├── tsconfig.test.json ├── vercel.json ├── vite.config.ts └── website ├── .gitignore ├── api └── ping.js ├── next-env.d.ts ├── next-sitemap.config.js ├── next.config.js ├── package.json ├── postcss.config.cjs ├── public ├── assets │ ├── img │ │ ├── ci │ │ │ └── diff.jpg │ │ ├── cli │ │ │ ├── coverage.png │ │ │ ├── coverageStats.png │ │ │ ├── demo.gif │ │ │ ├── demo.mp4 │ │ │ ├── demo.webm │ │ │ ├── diff.jpg │ │ │ ├── github.jpg │ │ │ ├── similar.jpg │ │ │ └── validate.jpg │ │ ├── diff-result.png │ │ ├── enterprise.png │ │ ├── favicon.png │ │ ├── favicon │ │ │ └── favicon.png │ │ ├── github │ │ │ ├── app-action.jpg │ │ │ ├── app-install.jpg │ │ │ ├── app-repositories.jpg │ │ │ ├── app-setup-plan.jpg │ │ │ ├── intercept.png │ │ │ └── summary.jpg │ │ ├── illustrations │ │ │ ├── bug-fixing.png │ │ │ ├── business-deal.png │ │ │ ├── code-review.svg │ │ │ ├── counting-stars.png │ │ │ ├── github.png │ │ │ ├── hacker-mindset.png │ │ │ ├── hive.png │ │ │ ├── investment.png │ │ │ ├── mail-box.png │ │ │ ├── new-ideas.png │ │ │ ├── real-time-collaboration.png │ │ │ ├── result.png │ │ │ ├── server-cluster.png │ │ │ ├── server.png │ │ │ ├── typewriter.png │ │ │ ├── winners.png │ │ │ └── yoga.png │ │ ├── just-logo.svg │ │ ├── logo-gray.svg │ │ ├── logo-slack.png │ │ ├── logo-white.svg │ │ ├── logo.svg │ │ ├── notifications │ │ │ ├── discord.png │ │ │ └── slack.png │ │ ├── og-image.png │ │ ├── serve-localhost.png │ │ ├── similar-output.png │ │ └── ui │ │ │ ├── arrows.svg │ │ │ ├── cover.svg │ │ │ ├── enterprise-cover.svg │ │ │ ├── features │ │ │ ├── annotations.png │ │ │ ├── diff.svg │ │ │ ├── github.svg │ │ │ ├── intercept.png │ │ │ ├── intercept.svg │ │ │ ├── notifications.png │ │ │ ├── schema-check.png │ │ │ └── validate.svg │ │ │ └── social │ │ │ ├── github.svg │ │ │ ├── medium.svg │ │ │ ├── spectrum.svg │ │ │ └── twitter.svg │ ├── subheader-logo-w.svg │ └── subheader-logo.svg ├── favicon.ico ├── favicon.png └── locales │ └── en │ └── common.json ├── src ├── components │ ├── diff │ │ ├── change.module.css │ │ ├── change.tsx │ │ └── index.tsx │ └── index-page.tsx └── pages │ ├── _app.tsx │ ├── _meta.ts │ ├── docs │ ├── _meta.ts │ ├── api │ │ ├── _meta.ts │ │ ├── documents.mdx │ │ └── schema.mdx │ ├── commands │ │ ├── _meta.ts │ │ ├── audit.mdx │ │ ├── coverage.mdx │ │ ├── diff.mdx │ │ ├── introspect.mdx │ │ ├── serve.mdx │ │ ├── similar.mdx │ │ └── validate.mdx │ ├── index.mdx │ ├── installation.mdx │ ├── migration-guides │ │ ├── _meta.ts │ │ ├── from-app-to-action.mdx │ │ ├── from-graphql-cli.mdx │ │ └── github.mdx │ ├── notifications.mdx │ ├── products │ │ ├── _meta.ts │ │ ├── action.mdx │ │ └── ci.mdx │ └── recipes │ │ ├── _meta.ts │ │ ├── annotations.mdx │ │ ├── endpoints.mdx │ │ ├── environments.mdx │ │ ├── intercept.mdx │ │ └── pull-requests.mdx │ └── index.mdx ├── tailwind.config.ts ├── theme.config.tsx └── tsconfig.json /.changeset/@graphql-inspector_action-2636-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@graphql-inspector/action': patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`@actions/github@6.0.0` 6 | ↗︎](https://www.npmjs.com/package/@actions/github/v/6.0.0) (from `4.0.0`, in `dependencies`) 7 | -------------------------------------------------------------------------------- /.changeset/@graphql-inspector_action-2767-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@graphql-inspector/action': patch 3 | --- 4 | dependencies updates: 5 | - Updated dependency [`probot@13.2.2` ↗︎](https://www.npmjs.com/package/probot/v/13.2.2) (from 6 | `12.3.3`, in `dependencies`) 7 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { 6 | "repo": "graphql-hive/graphql-inspector" 7 | } 8 | ], 9 | "commit": false, 10 | "access": "restricted", 11 | "baseBranch": "master", 12 | "ignore": ["website"], 13 | "snapshot": { 14 | "useCalculatedVersion": true, 15 | "prereleaseTemplate": "{tag}-{datetime}-{commit}" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.changeset/lazy-lemons-vanish.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@graphql-inspector/core': minor 3 | --- 4 | 5 | Updated removing field message to contain more information on why its a breaking change such as if 6 | the field is a union or indirectly referenced. 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [**.js, **.ts, **.json,**.yml] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | ignorePatterns: [ 4 | 'action/index.js', 5 | 'vite.config.ts', 6 | 'example/schemas/schema.js', 7 | 'example/rules/custom-rule.js', 8 | 'integration_tests/2027/unused-hotels.js', 9 | '__tests__', 10 | ], 11 | extends: ['@theguild', '@theguild/eslint-config/json', '@theguild/eslint-config/yml'], 12 | overrides: [ 13 | { 14 | files: ['website/**'], 15 | extends: '@theguild/eslint-config/react', 16 | }, 17 | { 18 | files: ['packages/**', 'website/**'], 19 | rules: { 20 | 'logical-assignment-operators': 'off', 21 | 'prefer-object-has-own': 'off', // enable in next major 22 | // TODO: enable following rules 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | '@typescript-eslint/no-non-null-assertion': 'off', 25 | '@typescript-eslint/ban-types': 'off', 26 | '@typescript-eslint/no-var-requires': 'off', 27 | 'n/no-restricted-import': 'off', 28 | '@typescript-eslint/prefer-optional-chain': 'off', 29 | 'yml/no-empty-mapping-value': 'off', 30 | '@typescript-eslint/parser': 'off', 31 | 'no-undef': 'off', 32 | 'unicorn/prefer-node-protocol': 'off', 33 | 'no-console': 'off', 34 | 'import/no-default-export': 'off', 35 | '@typescript-eslint/no-empty-object-type': 'off', 36 | '@typescript-eslint/no-require-imports': 'off', 37 | '@typescript-eslint/no-unused-vars': 'off', 38 | }, 39 | }, 40 | { 41 | files: [ 42 | '**/__tests__/**', 43 | '**/*.spec.ts', 44 | '**/*.test.ts', 45 | 'e2e/**', 46 | '**/__integration-tests__/**', 47 | ], 48 | rules: { 49 | 'import/extensions': 'off', 50 | }, 51 | }, 52 | { 53 | files: ['.github/**/*'], 54 | rules: { 55 | 'yml/plain-scalar': 'off', 56 | }, 57 | }, 58 | ], 59 | }; 60 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: kamilkisiela 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: http://paypal.me/kamilkisiela 13 | -------------------------------------------------------------------------------- /.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 11 | [Contributor Workflow](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md#a-typical-contributor-workflow)_ 12 | 13 | - [ ] 1. The issue provides a reproduction available on GitHub, Stackblitz or CodeSandbox 14 | > Make sure to fork this template and run `pnpm generate` in the terminal. 15 | > 16 | > Please make sure the Codegen and plugins version under `package.json` matches yours. 17 | - [ ] 2. A failing test has been provided 18 | - [ ] 3. A local solution has been provided 19 | - [ ] 4. A pull request is pending review 20 | 21 | --- 22 | 23 | **Describe the bug** 24 | 25 | 26 | 27 | **To Reproduce** Steps to reproduce the behavior: 28 | 29 | **Expected behavior** 30 | 31 | 32 | 33 | **Environment:** 34 | 35 | - OS: 36 | - `@graphql-inspector/...`: 37 | - `graphql`: 38 | - NodeJS: 39 | 40 | **Additional context** 41 | 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Have a question? 4 | url: https://github.com/graphql-hive/graphql-inspector/discussions/new 5 | about: 6 | Not sure about something? need help from the community? have a question to our team? please 7 | ask and answer questions here. 8 | -------------------------------------------------------------------------------- /.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/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 4 | the pull request._ 5 | 6 | ## Description 7 | 8 | Please include a summary of the change and which issue is fixed. Please also include relevant 9 | motivation and context. List any dependencies that are required for this change. 10 | 11 | Fixes # (issue) 12 | 13 | ## Type of change 14 | 15 | Please delete options that are not relevant. 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as 20 | expected) 21 | - [ ] This change requires a documentation update 22 | 23 | ## Screenshots/Sandbox (if appropriate/relevant): 24 | 25 | Adding links to sandbox or providing screenshots can help us understand more about this PR and take 26 | action on it as appropriate 27 | 28 | ## How Has This Been Tested? 29 | 30 | Please describe the tests that you ran to verify your changes. Provide instructions so we can 31 | reproduce. Please also list any relevant details for your test configuration 32 | 33 | - [ ] Test A 34 | - [ ] Test B 35 | 36 | **Test Environment**: 37 | 38 | - OS: 39 | - `@graphql-inspector/...`: 40 | - NodeJS: 41 | 42 | ## Checklist: 43 | 44 | - [ ] I have followed the 45 | [CONTRIBUTING](https://github.com/the-guild-org/Stack/blob/master/CONTRIBUTING.md) doc and the 46 | style guidelines of this project 47 | - [ ] I have performed a self-review of my own code 48 | - [ ] I have commented my code, particularly in hard-to-understand areas 49 | - [ ] I have made corresponding changes to the documentation 50 | - [ ] My changes generate no new warnings 51 | - [ ] I have added tests that prove my fix is effective or that my feature works 52 | - [ ] New and existing unit tests pass locally with my changes 53 | - [ ] Any dependent changes have been merged and published in downstream modules 54 | 55 | ## Further comments 56 | 57 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose 58 | the solution you did and what alternatives you considered, etc... 59 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "labels": ["automerge"], 4 | "prConcurrentLimit": 5, 5 | "prCreation": "immediate", 6 | "prHourlyLimit": 5, 7 | "timezone": "Europe/Warsaw", 8 | "schedule": ["after 10pm and before 6:00am"], 9 | "vulnerabilityAlerts": { 10 | "assignees": ["@kamilkisiela"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: Integration 2 | on: 3 | push: 4 | branches-ignore: 5 | - master 6 | pull_request: 7 | branches-ignore: 8 | - master 9 | jobs: 10 | Action: 11 | name: Test Inspector Action 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 16 | 17 | - name: Make changes to schema 18 | run: | 19 | sed -i '/title: String @deprecated(reason: "No more used")/d' ./example/schemas/schema.graphql 20 | sed -i 's/createdAt: String/createdAt: String!/g' ./example/schemas/schema.graphql 21 | sed -i 's/modifiedAt: String/modifiedAt: String!/g' ./example/schemas/schema.graphql 22 | sed -i 's/post: Post!/post(id: ID): Post!/g' ./example/schemas/schema.graphql 23 | cat ./example/schemas/schema.graphql 24 | 25 | - name: Run Inspector 26 | uses: ./ 27 | id: inspector 28 | with: 29 | experimental_merge: false 30 | schema: 'master:example/schemas/schema.graphql' 31 | rules: | 32 | suppressRemovalOfDeprecatedField 33 | ./example/rules/custom-rule.js 34 | 35 | - name: Validate result 36 | if: steps.inspector.outputs.changes != 4 37 | run: echo 'Expected 4 changes received ${{ steps.inspector.outputs.changes }}' && exit 1 38 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | push_to_registry: 12 | name: Push Docker image to Docker Hub 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 17 | 18 | - name: Extract metadata (tags, labels) for Docker 19 | id: meta 20 | uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4 21 | with: 22 | images: kamilkisiela/graphql-inspector 23 | 24 | - name: Login to Docker Hub 25 | uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2 26 | with: 27 | username: kamilkisiela 28 | password: ${{ secrets.DOCKER_PASSWORD }} 29 | 30 | - name: Build and push Docker image 31 | uses: docker/build-push-action@0a97817b6ade9f46837855d676c4cca3a2471fc9 # v4 32 | with: 33 | context: . 34 | push: true 35 | tags: ${{ steps.meta.outputs.tags }} 36 | labels: ${{ steps.meta.outputs.labels }} 37 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | release: 10 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 11 | with: 12 | npmTag: alpha 13 | buildScript: build 14 | packageManager: pnpm 15 | secrets: 16 | githubToken: ${{ secrets.GITHUB_TOKEN }} 17 | npmToken: ${{ secrets.NPM_TOKEN }} 18 | 19 | dependencies: 20 | uses: the-guild-org/shared-config/.github/workflows/changesets-dependencies.yaml@main 21 | secrets: 22 | githubToken: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | installDependencies: true 25 | packageManager: pnpm 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | 7 | jobs: 8 | stable: 9 | uses: the-guild-org/shared-config/.github/workflows/release-stable.yml@main 10 | with: 11 | releaseScript: release 12 | packageManager: pnpm 13 | secrets: 14 | githubToken: ${{ secrets.GITHUB_TOKEN }} 15 | npmToken: ${{ secrets.NPM_TOKEN }} 16 | -------------------------------------------------------------------------------- /.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: 15 | github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 16 | 'push' 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - uses: the-guild-org/shared-config/setup@main 24 | name: setup env 25 | with: 26 | nodeVersion: 23 27 | packageManager: pnpm 28 | 29 | - uses: the-guild-org/shared-config/website-cf@main 30 | name: build and deploy website 31 | env: 32 | NEXT_BASE_PATH: ${{ github.ref == 'refs/heads/master' && '/graphql/inspector' || '' }} 33 | SITE_URL: 34 | ${{ github.ref == 'refs/heads/master' && 'https://the-guild.dev/graphql/inspector' || 35 | ''}} 36 | with: 37 | cloudflareApiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 38 | cloudflareAccountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 39 | githubToken: ${{ secrets.GITHUB_TOKEN }} 40 | projectName: graphql-inspector 41 | prId: ${{ github.event.pull_request.number }} 42 | websiteDirectory: ./ 43 | buildScript: pnpm build && cd website && pnpm build 44 | artifactDir: website/out 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .bob/ 3 | .cache 4 | node_modules/ 5 | pnpm-error.log 6 | build/ 7 | .env 8 | private-key.pem 9 | .DS_Store 10 | website/**/pnpm-lock 11 | website/**/package-lock.json 12 | .now 13 | .vercel 14 | .idea/ 15 | .next/ 16 | .eslintcache 17 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v23 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | enable-pre-post-scripts=false 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .next/ 3 | lib/ 4 | .vscode/ 5 | action/ 6 | 7 | # action source can be prettified 8 | !packages/action 9 | 10 | # remove when prettier will support MDX (v3) 11 | website/src/pages/docs/api/schema.mdx 12 | .bob/ 13 | .changeset/*.md 14 | pnpm-lock.yaml 15 | website/src/pages/docs/migration-guides/from-graphql-cli.mdx 16 | packages/core/CHANGELOG.md 17 | packages/**/*.md 18 | .husky/ 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/build": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff", 8 | "useMendCheckNames": true 9 | }, 10 | "issueSettings": { 11 | "minSeverityLevel": "LOW", 12 | "issueType": "DEPENDENCY" 13 | } 14 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS build 2 | ENV NODE_ENV=development 3 | ENV DISTDIR=/usr/local/share/graphql-inspector 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN npm install -g pnpm@9.12.3 10 | RUN pnpm install 11 | RUN pnpm build 12 | 13 | FROM node:20-alpine AS dist 14 | ENV NODE_ENV=production 15 | ENV DISTDIR=/usr/local/share/graphql-inspector 16 | 17 | RUN mkdir /app 18 | WORKDIR /app 19 | 20 | COPY --from=build /app/packages "${DISTDIR}/packages" 21 | COPY --from=build /app/package.json ${DISTDIR} 22 | COPY --from=build /app/pnpm-workspace.yaml ${DISTDIR} 23 | 24 | RUN npm install -g pnpm@9.12.3 25 | 26 | WORKDIR ${DISTDIR} 27 | RUN pnpm install --prod 28 | RUN pnpm store prune 29 | 30 | RUN ln -s "${DISTDIR}/packages/cli/dist/cjs/index.js" /usr/local/bin/graphql-inspector \ 31 | && chmod +x /usr/local/bin/graphql-inspector 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: GraphQL Inspector 2 | author: Kamil Kisiela 3 | description: 'Tooling for GraphQL. Compare schemas, find breaking changes, find similar types.' 4 | branding: 5 | icon: search 6 | color: orange 7 | inputs: 8 | name: 9 | description: | 10 | The name of the check ("GraphQL Inspector" by default). 11 | In case of multiple GraphQL Inspector Actions, use `name` to prevent GitHub from overwriting results. 12 | For example, "Check Public API" and "Check Internal API". 13 | annotations: 14 | description: Use annotation (enabled by default) 15 | fail-on-breaking: 16 | description: Fail on breaking changes (enabled by default) 17 | approve-label: 18 | description: 19 | 'Label to mark Pull Request introducing breaking changes as safe and expected 20 | ("approved-breaking-change" by default)' 21 | schema: 22 | description: | 23 | Ref and Path to GraphQL Schema (e.g. "master:schema.graphql") 24 | 25 | * Ref is needed where 'endpoint' is not defined 26 | * Can be URL to the GraphQL API - should represent the "after" schema. Available only if 'endpoint' is defined. Has to start with http(s). 27 | required: true 28 | endpoint: 29 | description: | 30 | An url to the GraphQL API. It should represent the "before" schema. 31 | 32 | When using an endpoint, 'schema' should point to a file (without a reference - branch name for example) 33 | github-token: 34 | default: '${{ github.token }}' 35 | required: false 36 | description: 'Github Token. Use {{ github.token }} by default' 37 | experimental_merge: 38 | description: | 39 | Merge Pull Request's branch with the target branch to get the schema. 40 | Helps to get the correct state of schema when Pull Request is behind the target branch 41 | 42 | (enabled by default) 43 | rules: 44 | description: | 45 | Apply rules that change certain checks from Breaking to Dangerous. 46 | 47 | Rules should be listed as a multiline YAML string, not a list 48 | required: false 49 | onUsage: 50 | description: | 51 | Used only when the `considerUsage` rule is provided, otherwise this field is ignored. 52 | 53 | Should be a path to a JS file as described in https://the-guild.dev/graphql/inspector/docs/essentials/diff#considerusage 54 | required: false 55 | 56 | outputs: 57 | changes: 58 | description: Total number of changes 59 | runs: 60 | using: node20 61 | main: action/index.js 62 | -------------------------------------------------------------------------------- /action/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /assets/coverage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/assets/coverage.jpg -------------------------------------------------------------------------------- /assets/diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/assets/diff.jpg -------------------------------------------------------------------------------- /assets/github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/assets/github.jpg -------------------------------------------------------------------------------- /assets/similar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/assets/similar.jpg -------------------------------------------------------------------------------- /assets/validate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/assets/validate.jpg -------------------------------------------------------------------------------- /example/documents/depth-post.graphql: -------------------------------------------------------------------------------- 1 | query depthPost { 2 | post { 3 | id 4 | title @client 5 | author { 6 | id 7 | name 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/documents/post.graphql: -------------------------------------------------------------------------------- 1 | query post { 2 | post { 3 | ... on Post { 4 | id 5 | title 6 | } 7 | createdAtSomePoint 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "scripts": { 4 | "ci": "node ../packages/ci/dist/cjs/index.js", 5 | "coverage": "pnpm graphql-inspector coverage './documents/*.graphql' ./schemas/schema.graphql", 6 | "coverage:write": "pnpm graphql-inspector coverage './documents/*.graphql' ./schemas/schema.graphql --silent --write ./coverage.json", 7 | "diff": "pnpm graphql-inspector diff ./schemas/schema.graphql ./schemas/new.graphql", 8 | "diff:github": "pnpm graphql-inspector diff github:graphql-hive/graphql-inspector#master:example/schemas/schema.json ./schemas/new.graphql --token PERSONAL_ACCESS_TOKEN", 9 | "diff:json": "pnpm graphql-inspector diff ./schemas/schema.json ./schemas/new.json", 10 | "diff:master": "pnpm graphql-inspector diff git:master:example/schemas/schema.graphql ./schemas/schema.graphql", 11 | "diff:same": "pnpm graphql-inspector diff ./schemas/schema.js ./schemas/schema.graphql", 12 | "diff:same:json": "pnpm graphql-inspector diff ./schemas/schema.json ./schemas/schema.json", 13 | "graphql-inspector": "pnpm ci", 14 | "introspect": "pnpm graphql-inspector introspect ./schemas/schema.graphql", 15 | "introspect:write": "pnpm graphql-inspector introspect ./schemas/schema.graphql --write schema.graphql", 16 | "open-help": "pnpm graphql-inspector --help", 17 | "serve": "pnpm graphql-inspector serve ./schemas/schema.graphql", 18 | "similar": "pnpm graphql-inspector similar ./schemas/similar.graphql", 19 | "similar:loose": "pnpm graphql-inspector similar ./schemas/similar.graphql --threshold 0.1", 20 | "similar:type": "pnpm graphql-inspector similar ./schemas/similar.graphql --type Post", 21 | "similar:write": "pnpm graphql-inspector similar ./schemas/similar.graphql --write ./similar.json", 22 | "ui": "pnpm graphql-inspector ui", 23 | "validate": "pnpm graphql-inspector validate './documents/*.graphql' ./schemas/schema.graphql", 24 | "validate:depth": "pnpm graphql-inspector validate './documents/depth-post.graphql' ./schemas/deep.graphql --maxDepth 1", 25 | "validate:none": "pnpm graphql-inspector validate './no-documents/*.graphql' ./schemas/schema.graphql" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/rules/custom-rule.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ changes }) => { 2 | return changes.map(c => ({ 3 | ...c, 4 | criticality: { ...c.criticality, level: 'DANGEROUS' }, 5 | })); 6 | }; 7 | -------------------------------------------------------------------------------- /example/schemas/deep.graphql: -------------------------------------------------------------------------------- 1 | type Post { 2 | id: ID! 3 | title: String! 4 | createdAt: String! 5 | author: User! 6 | } 7 | 8 | type User { 9 | id: ID! 10 | name: String! 11 | } 12 | 13 | type Query { 14 | post: Post! 15 | } 16 | -------------------------------------------------------------------------------- /example/schemas/new-valid.graphql: -------------------------------------------------------------------------------- 1 | type Post { 2 | id: ID 3 | title: String @deprecated(reason: "No more used") 4 | createdAt: String! 5 | modifiedAt: String 6 | } 7 | 8 | type Query { 9 | post: Post! 10 | posts: [Post!] 11 | } 12 | -------------------------------------------------------------------------------- /example/schemas/new.graphql: -------------------------------------------------------------------------------- 1 | type Post { 2 | id: ID! 3 | title: String! 4 | """ 5 | Date of Creation in ISO 6 | """ 7 | createdAt: String! 8 | } 9 | 10 | type Query { 11 | post: Post! 12 | } 13 | -------------------------------------------------------------------------------- /example/schemas/schema.graphql: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED 2 | 3 | type Post { 4 | id: ID 5 | title: String @deprecated(reason: "No more used") 6 | """ 7 | Date of Creation in ISO 8 | """ 9 | createdAt: String 10 | modifiedAt: String 11 | } 12 | 13 | type Query { 14 | post: Post! 15 | posts: [Post!] 16 | } 17 | -------------------------------------------------------------------------------- /example/schemas/schema.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require('node:fs'); 2 | const { resolve } = require('node:path'); 3 | 4 | module.exports = readFileSync(resolve(__dirname, './schema.graphql'), 'utf8'); 5 | -------------------------------------------------------------------------------- /example/schemas/similar.graphql: -------------------------------------------------------------------------------- 1 | type Post { 2 | id: ID 3 | title: String 4 | createdAt: String 5 | modifiedAt: String 6 | } 7 | 8 | type BlogPost { 9 | title: String 10 | createdAt: String 11 | id: ID 12 | } 13 | 14 | type MailPost { 15 | title: String 16 | createdAt: String 17 | } 18 | 19 | type EmailPost { 20 | title: String 21 | createdAt: String 22 | modifiedAt: String 23 | } 24 | 25 | type Comment { 26 | id: ID 27 | } 28 | 29 | type Query { 30 | post: Post! 31 | } 32 | -------------------------------------------------------------------------------- /integration_tests/1985/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "issue-1985", 3 | "type": "module" 4 | } 5 | -------------------------------------------------------------------------------- /integration_tests/1985/schema.js: -------------------------------------------------------------------------------- 1 | import { buildSchema } from 'graphql'; 2 | 3 | export const schema = buildSchema(`type Query { foo: String }`); 4 | -------------------------------------------------------------------------------- /integration_tests/1991/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | isLoggedIn: Boolean! 3 | me: User! 4 | } 5 | 6 | type User { 7 | id: ID! 8 | } 9 | -------------------------------------------------------------------------------- /integration_tests/1991/two-operations.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'graphql-tag'; 2 | 3 | gql(/* GraphQL */ ` 4 | query isAuthenticated { 5 | isLoggedIn 6 | } 7 | `); 8 | 9 | export const UserFrgmnt = gql(/* GraphQL */ ` 10 | fragment UserFrgmnt on User { 11 | id 12 | } 13 | `); 14 | -------------------------------------------------------------------------------- /integration_tests/2001/operations/companies.graphql: -------------------------------------------------------------------------------- 1 | query getCompanies { 2 | companies { 3 | id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integration_tests/2001/operations/hotels.graphql: -------------------------------------------------------------------------------- 1 | query getHotels { 2 | hotels { 3 | id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integration_tests/2001/schema.graphql: -------------------------------------------------------------------------------- 1 | type Company { 2 | id: ID! 3 | } 4 | 5 | type Hotel { 6 | id: ID! 7 | } 8 | 9 | type Query { 10 | companies: [Company]! 11 | hotels: [Hotel]! 12 | } 13 | -------------------------------------------------------------------------------- /integration_tests/2027/schema-after.graphql: -------------------------------------------------------------------------------- 1 | type Company { 2 | id: ID! 3 | } 4 | 5 | type Query { 6 | companies: [Company]! 7 | } 8 | -------------------------------------------------------------------------------- /integration_tests/2027/schema-before.graphql: -------------------------------------------------------------------------------- 1 | type Company { 2 | id: ID! 3 | } 4 | 5 | type Hotel { 6 | id: ID! 7 | } 8 | 9 | type Query { 10 | companies: [Company]! 11 | hotels: [Hotel]! 12 | } 13 | -------------------------------------------------------------------------------- /integration_tests/2027/unused-hotels.js: -------------------------------------------------------------------------------- 1 | const BREAKING = { 2 | YES: false, 3 | NOT: true, 4 | }; 5 | 6 | /** 7 | * @type import('@graphql-inspector/core').UsageHandler 8 | */ 9 | const checkUsage = changes => { 10 | return changes.map(change => { 11 | if (change.type === 'Hotel') { 12 | return BREAKING.NOT; 13 | } 14 | 15 | if (change.type === 'Query' && change.field === 'hotels') { 16 | return BREAKING.NOT; 17 | } 18 | 19 | return BREAKING.YES; 20 | }); 21 | }; 22 | 23 | module.exports = checkUsage; 24 | -------------------------------------------------------------------------------- /integration_tests/2088/new.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | foo: String 3 | } 4 | -------------------------------------------------------------------------------- /integration_tests/2088/old.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | foo: String 3 | bar: String @deprecated 4 | } 5 | -------------------------------------------------------------------------------- /integration_tests/2108/excluded/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | bar: String 3 | } 4 | -------------------------------------------------------------------------------- /integration_tests/2108/included/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | foo: String 3 | } 4 | -------------------------------------------------------------------------------- /integration_tests/2239/newSchema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | newQuery: Int! 3 | } 4 | -------------------------------------------------------------------------------- /integration_tests/2239/oldSchema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | oldQuery: OldType @deprecated(reason: "use newQuery") 3 | newQuery: Int! 4 | } 5 | 6 | type OldType { 7 | field: String! 8 | } 9 | -------------------------------------------------------------------------------- /packages/action/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /packages/action/helpers/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from 'probot'; 2 | import { ErrorHandler } from './types.js'; 3 | 4 | const key = Symbol.for('inspector-diagnostics'); 5 | 6 | export function setDiagnostics( 7 | app: Probot, 8 | diagnostics: { onError: ErrorHandler; release: string }, 9 | ) { 10 | (app as any)[key] = diagnostics; 11 | } 12 | 13 | export function getDiagnostics(app: Probot) { 14 | return (app as any)[key]; 15 | } 16 | -------------------------------------------------------------------------------- /packages/action/helpers/errors.ts: -------------------------------------------------------------------------------- 1 | export class MissingConfigError extends Error { 2 | constructor() { 3 | super( 4 | [ 5 | 'Failed to find a configuration', 6 | '', 7 | 'https://graphql-inspector.com/docs/products/github#usage', 8 | ].join('\n'), 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/action/helpers/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from 'probot'; 2 | 3 | export interface Logger { 4 | log(msg: string): void; 5 | info(msg: string): void; 6 | warn(msg: string): void; 7 | error(msg: string | Error, error?: Error): void; 8 | } 9 | 10 | export function createLogger(label: string, context: Context, release: string): Logger { 11 | const id = Math.random().toString(16).substr(2, 5); 12 | const prefix = (msg: string) => `${label} ${id} - release-${release.substr(0, 7)} : ${msg}`; 13 | 14 | return { 15 | log(msg: string) { 16 | context.log.info(prefix(msg)); 17 | }, 18 | info(msg: string) { 19 | context.log.info(prefix(msg)); 20 | }, 21 | warn(msg: string) { 22 | context.log.warn(prefix(msg)); 23 | }, 24 | error(msg: string | Error, error?: any) { 25 | if (error) { 26 | console.error(error); 27 | } 28 | 29 | context.log.error(prefix(msg instanceof Error ? msg.message : msg)); 30 | }, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/action/helpers/schema.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, Source } from 'graphql'; 2 | 3 | export function produceSchema(source: Source) { 4 | try { 5 | if (!source.body.trim().length) { 6 | throw new Error(`Content is empty`); 7 | } 8 | 9 | return buildSchema(source, { 10 | assumeValid: true, 11 | assumeValidSDL: true, 12 | }); 13 | } catch (e: any) { 14 | throw new Error(`Failed to parse "${source.name}": ${e.message}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/action/helpers/types.ts: -------------------------------------------------------------------------------- 1 | import { Change } from '@graphql-inspector/core'; 2 | 3 | export type ErrorHandler = (error: Error) => void; 4 | 5 | export interface ActionResult { 6 | conclusion: CheckConclusion; 7 | annotations?: Annotation[]; 8 | changes?: Change[]; 9 | } 10 | 11 | export interface Annotation { 12 | path: string; 13 | start_line: number; 14 | end_line: number; 15 | annotation_level: AnnotationLevel; 16 | message: string; 17 | title?: string; 18 | raw_details?: string; 19 | start_column?: number; 20 | end_column?: number; 21 | } 22 | 23 | export enum AnnotationLevel { 24 | Failure = 'failure', 25 | Warning = 'warning', 26 | Notice = 'notice', 27 | } 28 | 29 | export enum CheckStatus { 30 | InProgress = 'in_progress', 31 | Completed = 'completed', 32 | } 33 | 34 | export enum CheckConclusion { 35 | Success = 'success', 36 | Neutral = 'neutral', 37 | Failure = 'failure', 38 | } 39 | 40 | export interface PullRequest { 41 | base: { ref: string }; 42 | url: string; 43 | id: number; 44 | number: number; 45 | labels?: Array<{ name: string }>; 46 | } 47 | -------------------------------------------------------------------------------- /packages/action/src/action.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import { run } from './run.js'; 3 | 4 | (global as any).navigator = { 5 | userAgent: 'node.js', 6 | }; 7 | 8 | run().catch(e => { 9 | core.setFailed(e.message || e); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/action/src/checks.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import { CheckConclusion } from '../helpers/types.js'; 4 | import { OctokitInstance } from './types.js'; 5 | import { batch } from './utils.js'; 6 | 7 | type UpdateCheckRunOptions = Required< 8 | Pick< 9 | NonNullable[0]>, 10 | 'conclusion' | 'output' 11 | > 12 | >; 13 | 14 | export async function updateCheckRun( 15 | octokit: OctokitInstance, 16 | checkId: number, 17 | { conclusion, output }: UpdateCheckRunOptions, 18 | ) { 19 | core.info(`Updating check: ${checkId}`); 20 | 21 | const { title, summary, annotations = [] } = output; 22 | const batches = batch(annotations, 50); 23 | 24 | core.info(`annotations to be sent: ${annotations.length}`); 25 | 26 | await octokit.rest.checks.update({ 27 | check_run_id: checkId, 28 | completed_at: new Date().toISOString(), 29 | status: 'completed', 30 | ...github.context.repo, 31 | conclusion, 32 | output: { 33 | title, 34 | summary, 35 | }, 36 | }); 37 | 38 | try { 39 | await Promise.all( 40 | batches.map(async chunk => { 41 | await octokit.rest.checks.update({ 42 | check_run_id: checkId, 43 | ...github.context.repo, 44 | output: { 45 | title, 46 | summary, 47 | annotations: chunk, 48 | }, 49 | } as any); 50 | core.info(`annotations sent (${chunk.length})`); 51 | }), 52 | ); 53 | } catch (error) { 54 | core.error(`failed to send annotations: ${error}`); 55 | throw error; 56 | } 57 | 58 | // Fail 59 | if (conclusion === CheckConclusion.Failure) { 60 | return core.setFailed(output.title!); 61 | } 62 | 63 | // Success or Neutral 64 | } 65 | -------------------------------------------------------------------------------- /packages/action/src/files.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { resolve } from 'path'; 3 | import * as core from '@actions/core'; 4 | import { OctokitInstance } from './types.js'; 5 | 6 | export function fileLoader({ 7 | octokit, 8 | owner, 9 | repo, 10 | }: { 11 | octokit: OctokitInstance; 12 | owner: string; 13 | repo: string; 14 | }) { 15 | const query = /* GraphQL */ ` 16 | query GetFile($repo: String!, $owner: String!, $expression: String!) { 17 | repository(name: $repo, owner: $owner) { 18 | object(expression: $expression) { 19 | ... on Blob { 20 | isTruncated 21 | oid 22 | text 23 | } 24 | } 25 | } 26 | } 27 | `; 28 | 29 | return async function loadFile(file: { 30 | ref: string; 31 | path: string; 32 | workspace?: string; 33 | }): Promise { 34 | if (file.workspace) { 35 | return readFileSync(resolve(file.workspace, file.path), 'utf8'); 36 | } 37 | const result: any = await octokit.graphql(query, { 38 | repo, 39 | owner, 40 | expression: `${file.ref}:${file.path}`, 41 | }); 42 | core.info(`Query ${file.ref}:${file.path} from ${owner}/${repo}`); 43 | 44 | try { 45 | if (result?.repository?.object?.oid && result?.repository?.object?.isTruncated) { 46 | const oid = result?.repository?.object?.oid; 47 | const getBlobResponse = await octokit.rest.git.getBlob({ 48 | owner, 49 | repo, 50 | file_sha: oid, 51 | }); 52 | 53 | if (getBlobResponse?.data?.content) { 54 | return Buffer.from(getBlobResponse?.data?.content, 'base64').toString('utf-8'); 55 | } 56 | 57 | throw new Error('getBlobResponse.data.content is null'); 58 | } 59 | 60 | if (result?.repository?.object?.text) { 61 | if (result?.repository?.object?.isTruncated === false) { 62 | return result.repository.object.text; 63 | } 64 | 65 | throw new Error('result.repository.object.text is truncated and oid is null'); 66 | } 67 | 68 | throw new Error('result.repository.object.text is null'); 69 | } catch (error) { 70 | console.log(result); 71 | console.error(error); 72 | throw new Error(`Failed to load '${file.path}' (ref: ${file.ref})`); 73 | } 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /packages/action/src/git.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import * as github from '@actions/github'; 3 | import { OctokitInstance } from './types.js'; 4 | 5 | export function getCurrentCommitSha() { 6 | const sha = execSync(`git rev-parse HEAD`).toString().trim(); 7 | 8 | try { 9 | const msg = execSync(`git show ${sha} -s --format=%s`).toString().trim(); 10 | const PR_MSG = /Merge (\w+) into \w+/i; 11 | 12 | if (PR_MSG.test(msg)) { 13 | const result = PR_MSG.exec(msg); 14 | 15 | if (result) { 16 | return result[1]; 17 | } 18 | } 19 | } catch (e) { 20 | // 21 | } 22 | 23 | return sha; 24 | } 25 | 26 | export async function getAssociatedPullRequest(octokit: OctokitInstance, commitSha: string) { 27 | const result = await octokit.request('GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls', { 28 | ...github.context.repo, 29 | commit_sha: commitSha, 30 | mediaType: { 31 | format: 'json', 32 | previews: ['groot'], 33 | }, 34 | }); 35 | return result.data.length > 0 ? result.data[0] : null; 36 | } 37 | -------------------------------------------------------------------------------- /packages/action/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as core from '@actions/core'; 3 | import { run } from './run.js'; 4 | 5 | run().catch(e => { 6 | core.setFailed(e.message || e); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/action/src/types.ts: -------------------------------------------------------------------------------- 1 | import { getOctokit } from '@actions/github'; 2 | 3 | export type OctokitInstance = ReturnType; 4 | -------------------------------------------------------------------------------- /packages/action/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'fs'; 2 | import * as core from '@actions/core'; 3 | import { ensureAbsolute } from '@graphql-inspector/commands'; 4 | import { DiffRule, Rule } from '@graphql-inspector/core'; 5 | 6 | export function batch(items: T[], limit: number): T[][] { 7 | const batches: T[][] = []; 8 | const batchesNum = Math.ceil(items.length / limit); 9 | 10 | // We still want to update check-run and send empty annotations 11 | if (batchesNum === 0) { 12 | return [[]]; 13 | } 14 | 15 | for (let i = 0; i < batchesNum; i++) { 16 | const start = i * limit; 17 | const end = start + limit; 18 | 19 | batches.push(items.slice(start, end)); 20 | } 21 | 22 | return batches; 23 | } 24 | 25 | /** 26 | * Treats non-falsy value as true 27 | */ 28 | export function castToBoolean(value: string | boolean, defaultValue?: boolean): boolean { 29 | if (typeof value === 'boolean') { 30 | return value; 31 | } 32 | 33 | if (value === 'true' || value === 'false') { 34 | return value === 'true'; 35 | } 36 | 37 | if (typeof defaultValue === 'boolean') { 38 | return defaultValue; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | export function getInputAsArray(name: string, options?: core.InputOptions): string[] { 45 | return core 46 | .getInput(name, options) 47 | .split('\n') 48 | .map(s => s.trim()) 49 | .filter(x => x !== ''); 50 | } 51 | 52 | export function resolveRule(name: string): Rule | undefined { 53 | const filepath = ensureAbsolute(name); 54 | 55 | if (existsSync(filepath)) { 56 | return require(filepath); 57 | } 58 | 59 | return DiffRule[name as keyof typeof DiffRule]; 60 | } 61 | -------------------------------------------------------------------------------- /packages/ci/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import yargs, { Argv } from 'yargs'; 3 | import { useCommands } from '@graphql-inspector/commands'; 4 | import { availableCommands, useConfig } from '@graphql-inspector/config'; 5 | import { useLoaders } from '@graphql-inspector/loaders'; 6 | import { Logger } from '@graphql-inspector/logger'; 7 | 8 | async function main() { 9 | const config = await useConfig(); 10 | const loaders = useLoaders(config); 11 | const commands = useCommands({ config, loaders }); 12 | 13 | const root: Argv = yargs 14 | .scriptName('graphql-inspector') 15 | .detectLocale(false) 16 | .epilog('Visit https://graphql-inspector.com for more information') 17 | .version() 18 | .options({ 19 | r: { 20 | alias: 'require', 21 | describe: 'Require modules', 22 | type: 'array', 23 | }, 24 | t: { 25 | alias: 'token', 26 | describe: 'Access Token', 27 | type: 'string', 28 | }, 29 | h: { 30 | alias: 'header', 31 | describe: 'Http Header', 32 | type: 'array', 33 | }, 34 | hl: { 35 | alias: 'left-header', 36 | describe: 'Http Header - Left', 37 | type: 'array', 38 | }, 39 | hr: { 40 | alias: 'right-header', 41 | describe: 'Http Header - Right', 42 | type: 'array', 43 | }, 44 | }); 45 | 46 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions 47 | commands 48 | .reduce((cli, cmd) => cli.command(cmd), root) 49 | .demandCommand() 50 | .recommendCommands() 51 | .help() 52 | .showHelpOnFail(false) 53 | .fail((msg, error) => { 54 | if (msg.includes('Unknown argument:')) { 55 | const commandName = msg.replace('Unknown argument: ', '').toLowerCase(); 56 | 57 | Logger.error(`Command '${commandName}' not found`); 58 | 59 | if (availableCommands.includes(commandName)) { 60 | Logger.log(` Try to install @graphql-inspector/${commandName}-command`); 61 | } 62 | } else if (msg.includes('Not enough')) { 63 | Logger.error(msg); 64 | Logger.info('Specify --help for available options'); 65 | } else { 66 | Logger.error(msg); 67 | } 68 | 69 | if (error) { 70 | throw error; 71 | } 72 | 73 | process.exit(1); 74 | }) 75 | .strict(true).argv; 76 | } 77 | 78 | main(); 79 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /packages/cli/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/packages/cli/demo.gif -------------------------------------------------------------------------------- /packages/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import yargs, { Argv } from 'yargs'; 3 | import { useCommands } from '@graphql-inspector/commands'; 4 | import { useLoaders } from '@graphql-inspector/loaders'; 5 | 6 | async function main() { 7 | const config = { 8 | loaders: ['code', 'git', 'github', 'graphql', 'json', 'url'], 9 | commands: ['docs', 'serve', 'diff', 'validate', 'coverage', 'introspect', 'similar', 'audit'], 10 | }; 11 | const loaders = useLoaders(config); 12 | const commands = useCommands({ config, loaders }); 13 | 14 | const root: Argv = yargs 15 | .scriptName('graphql-inspector') 16 | .detectLocale(false) 17 | .epilog('Visit https://graphql-inspector.com for more information') 18 | .version() 19 | .options({ 20 | r: { 21 | alias: 'require', 22 | describe: 'Require modules', 23 | type: 'array', 24 | }, 25 | t: { 26 | alias: 'token', 27 | describe: 'Access Token', 28 | type: 'string', 29 | }, 30 | h: { 31 | alias: 'header', 32 | describe: 'Http Header', 33 | type: 'array', 34 | }, 35 | hl: { 36 | alias: 'left-header', 37 | describe: 'Http Header - Left', 38 | type: 'array', 39 | }, 40 | hr: { 41 | alias: 'right-header', 42 | describe: 'Http Header - Right', 43 | type: 'array', 44 | }, 45 | }); 46 | 47 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions 48 | commands 49 | .reduce((cli, cmd) => cli.command(cmd), root) 50 | .help() 51 | .showHelpOnFail(false).argv; 52 | } 53 | 54 | main(); 55 | -------------------------------------------------------------------------------- /packages/commands/audit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/audit-command", 3 | "version": "5.0.8", 4 | "type": "module", 5 | "description": "Audit Documents in GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/audit" 10 | }, 11 | "author": { 12 | "name": "Laurin Quast", 13 | "email": "laurinquast@googlemail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "@graphql-tools/utils": "10.8.6", 71 | "cli-table3": "0.6.3", 72 | "tslib": "2.6.2" 73 | }, 74 | "devDependencies": { 75 | "graphql": "16.9.0" 76 | }, 77 | "publishConfig": { 78 | "directory": "dist", 79 | "access": "public" 80 | }, 81 | "sideEffects": false, 82 | "typescript": { 83 | "definition": "dist/typings/index.d.ts" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/commands/commands/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/commands", 3 | "version": "5.0.4", 4 | "type": "module", 5 | "description": "Plugin system for commands in GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/commands" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "@graphql-inspector/config": "^4.0.0", 65 | "@graphql-inspector/loaders": "^4.0.5", 66 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", 67 | "yargs": "17.7.2" 68 | }, 69 | "dependencies": { 70 | "tslib": "2.6.2" 71 | }, 72 | "publishConfig": { 73 | "directory": "dist", 74 | "access": "public" 75 | }, 76 | "sideEffects": false, 77 | "typescript": { 78 | "definition": "dist/typings/index.d.ts" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/commands/coverage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/coverage-command", 3 | "version": "6.1.2", 4 | "type": "module", 5 | "description": "Schema Coverage in GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "kamilkisgraphql-hiveiela/graphql-inspector", 9 | "directory": "packages/commands/coverage" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "@graphql-tools/utils": "10.8.6", 71 | "tslib": "2.6.2" 72 | }, 73 | "devDependencies": { 74 | "graphql": "16.9.0" 75 | }, 76 | "publishConfig": { 77 | "directory": "dist", 78 | "access": "public" 79 | }, 80 | "sideEffects": false, 81 | "typescript": { 82 | "definition": "dist/typings/index.d.ts" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/commands/diff/__tests__/assets/on-complete.cjs: -------------------------------------------------------------------------------- 1 | module.exports = () => process.exit(2); 2 | -------------------------------------------------------------------------------- /packages/commands/diff/__tests__/assets/rule.cjs: -------------------------------------------------------------------------------- 1 | module.exports = ({ changes }) => changes; 2 | -------------------------------------------------------------------------------- /packages/commands/diff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/diff-command", 3 | "version": "5.0.8", 4 | "type": "module", 5 | "description": "Compare GraphQL Schemas", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/diff" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "tslib": "2.6.2" 71 | }, 72 | "publishConfig": { 73 | "directory": "dist", 74 | "access": "public" 75 | }, 76 | "sideEffects": false, 77 | "typescript": { 78 | "definition": "dist/typings/index.d.ts" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/commands/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/docs-command", 3 | "version": "5.0.4", 4 | "type": "module", 5 | "description": "Open GraphQL Inspector Documentation", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/docs" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "open": "8.4.2", 69 | "tslib": "2.6.2" 70 | }, 71 | "publishConfig": { 72 | "directory": "dist", 73 | "access": "public" 74 | }, 75 | "sideEffects": false, 76 | "typescript": { 77 | "definition": "dist/typings/index.d.ts" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/commands/docs/src/index.ts: -------------------------------------------------------------------------------- 1 | import open from 'open'; 2 | import { createCommand } from '@graphql-inspector/commands'; 3 | 4 | export default createCommand((): any => { 5 | return { 6 | command: ['docs', 'website'], 7 | describe: 'Open Documentation', 8 | async handler() { 9 | return await open('https://graphql-inspector.com'); 10 | }, 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /packages/commands/introspect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/introspect-command", 3 | "version": "5.0.8", 4 | "type": "module", 5 | "description": "Introspects GraphQL Schema", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/introspect" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "tslib": "2.6.2" 71 | }, 72 | "publishConfig": { 73 | "directory": "dist", 74 | "access": "public" 75 | }, 76 | "sideEffects": false, 77 | "typescript": { 78 | "definition": "dist/typings/index.d.ts" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/commands/serve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/serve-command", 3 | "version": "5.0.6", 4 | "type": "module", 5 | "description": "Serves GraphQL Schemma", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/docs" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/logger": "workspace:*", 69 | "graphql-yoga": "5.7.0", 70 | "open": "8.4.2", 71 | "tslib": "2.6.2" 72 | }, 73 | "devDependencies": { 74 | "graphql": "16.9.0" 75 | }, 76 | "publishConfig": { 77 | "directory": "dist", 78 | "access": "public" 79 | }, 80 | "sideEffects": false, 81 | "typescript": { 82 | "definition": "dist/typings/index.d.ts" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/commands/serve/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from 'http'; 2 | import { createYoga } from 'graphql-yoga'; 3 | import open from 'open'; 4 | import { 5 | CommandFactory, 6 | createCommand, 7 | GlobalArgs, 8 | parseGlobalArgs, 9 | } from '@graphql-inspector/commands'; 10 | import { Logger } from '@graphql-inspector/logger'; 11 | import { fake } from './fake.js'; 12 | 13 | export { CommandFactory }; 14 | 15 | export default createCommand< 16 | {}, 17 | { 18 | schema: string; 19 | port: number; 20 | } & GlobalArgs 21 | >(api => { 22 | const { loaders } = api; 23 | 24 | return { 25 | command: 'serve ', 26 | describe: 'Runs server with fake data based on schema', 27 | builder(yargs) { 28 | return yargs 29 | .positional('schema', { 30 | describe: 'Point to a schema', 31 | type: 'string', 32 | demandOption: true, 33 | }) 34 | .options({ 35 | port: { 36 | alias: 'p', 37 | describe: 'Port', 38 | type: 'number', 39 | default: 4000, 40 | }, 41 | }); 42 | }, 43 | async handler(args) { 44 | const { headers, token } = parseGlobalArgs(args); 45 | const apolloFederation = args.federation || false; 46 | const aws = args.aws || false; 47 | const method = args.method?.toUpperCase() || 'POST'; 48 | 49 | const schema = await loaders.loadSchema( 50 | args.schema, 51 | { 52 | headers, 53 | token, 54 | method, 55 | }, 56 | apolloFederation, 57 | aws, 58 | ); 59 | 60 | const port = args.port; 61 | 62 | try { 63 | fake(schema); 64 | 65 | const yoga = createYoga({ 66 | schema, 67 | logging: false, 68 | }); 69 | 70 | const server = createServer(yoga); 71 | 72 | await new Promise(resolve => server.listen(port, () => resolve())); 73 | 74 | const url = `http://localhost:${port}/graphql`; 75 | Logger.success(`GraphQL API: ${url}`); 76 | await open(url); 77 | 78 | const shutdown = () => { 79 | server.close(() => { 80 | process.exit(0); 81 | }); 82 | }; 83 | 84 | process.on('SIGINT', shutdown); 85 | process.on('SIGTERM', shutdown); 86 | } catch (e: any) { 87 | Logger.error(e.message || e); 88 | } 89 | }, 90 | }; 91 | }); 92 | -------------------------------------------------------------------------------- /packages/commands/similar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/similar-command", 3 | "version": "5.0.8", 4 | "type": "module", 5 | "description": "Find similar types in GraphQL Schema", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/similar" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "tslib": "2.6.2" 71 | }, 72 | "publishConfig": { 73 | "directory": "dist", 74 | "access": "public" 75 | }, 76 | "sideEffects": false, 77 | "typescript": { 78 | "definition": "dist/typings/index.d.ts" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/commands/validate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/validate-command", 3 | "version": "5.0.8", 4 | "type": "module", 5 | "description": "Validate Documents in GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/commands/validate" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-command", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-inspector/commands": "workspace:*", 68 | "@graphql-inspector/core": "workspace:*", 69 | "@graphql-inspector/logger": "workspace:*", 70 | "@graphql-tools/utils": "10.8.6", 71 | "tslib": "2.6.2" 72 | }, 73 | "devDependencies": { 74 | "graphql": "16.9.0" 75 | }, 76 | "publishConfig": { 77 | "directory": "dist", 78 | "access": "public" 79 | }, 80 | "sideEffects": false, 81 | "typescript": { 82 | "definition": "dist/typings/index.d.ts" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/config", 3 | "version": "4.0.2", 4 | "type": "module", 5 | "repository": { 6 | "type": "git", 7 | "url": "graphql-hive/graphql-inspector", 8 | "directory": "packages/config" 9 | }, 10 | "author": { 11 | "name": "Kamil Kisiela", 12 | "email": "kamil.kisiela@gmail.com", 13 | "url": "https://github.com/kamilkisiela" 14 | }, 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">=18.0.0" 18 | }, 19 | "main": "dist/cjs/index.js", 20 | "module": "dist/esm/index.js", 21 | "exports": { 22 | ".": { 23 | "require": { 24 | "types": "./dist/typings/index.d.cts", 25 | "default": "./dist/cjs/index.js" 26 | }, 27 | "import": { 28 | "types": "./dist/typings/index.d.ts", 29 | "default": "./dist/esm/index.js" 30 | }, 31 | "default": { 32 | "types": "./dist/typings/index.d.ts", 33 | "default": "./dist/esm/index.js" 34 | } 35 | }, 36 | "./*": { 37 | "require": { 38 | "types": "./dist/typings/*.d.cts", 39 | "default": "./dist/cjs/*.js" 40 | }, 41 | "import": { 42 | "types": "./dist/typings/*.d.ts", 43 | "default": "./dist/esm/*.js" 44 | }, 45 | "default": { 46 | "types": "./dist/typings/*.d.ts", 47 | "default": "./dist/esm/*.js" 48 | } 49 | }, 50 | "./package.json": "./package.json" 51 | }, 52 | "typings": "dist/typings/index.d.ts", 53 | "scripts": { 54 | "prepack": "bob prepack" 55 | }, 56 | "peerDependencies": { 57 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 58 | }, 59 | "dependencies": { 60 | "tslib": "2.6.2" 61 | }, 62 | "publishConfig": { 63 | "directory": "dist", 64 | "access": "public" 65 | }, 66 | "sideEffects": false, 67 | "typescript": { 68 | "definition": "dist/typings/index.d.ts" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/config/src/index.ts: -------------------------------------------------------------------------------- 1 | // Right now everything is hardcoded for better UX but this may change in future. 2 | // It's just easier to leave it this way 3 | 4 | export interface InspectorConfig { 5 | loaders: string[]; 6 | commands: string[]; 7 | } 8 | 9 | export const availableCommands = [ 10 | 'coverage', 11 | 'diff', 12 | 'docs', 13 | 'introspect', 14 | 'serve', 15 | 'similar', 16 | 'validate', 17 | ]; 18 | 19 | export const availableLoaders = ['code', 'git', 'github', 'graphql', 'json', 'url']; 20 | 21 | export async function useConfig(): Promise { 22 | return { 23 | loaders: ensureList(discoverLoaders(availableLoaders), 'loaders'), 24 | commands: ensureList(discoverCommands(availableCommands), 'commands'), 25 | }; 26 | } 27 | 28 | function moduleExists(name: string) { 29 | try { 30 | require.resolve(name); 31 | return true; 32 | } catch (error) { 33 | return false; 34 | } 35 | } 36 | 37 | function discoverLoaders(loaders: string[]) { 38 | return loaders.filter(name => moduleExists(`@graphql-inspector/${name}-loader`)); 39 | } 40 | 41 | function discoverCommands(commands: string[]) { 42 | return commands.filter(name => moduleExists(`@graphql-inspector/${name}-command`)); 43 | } 44 | 45 | function ensureList(list: any, path: string): T[] { 46 | if (!list) { 47 | return []; 48 | } 49 | 50 | if (Array.isArray(list)) { 51 | return list; 52 | } 53 | 54 | throw new Error(`Value of ${path} expected to be a list`); 55 | } 56 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | src/ 3 | tsconfig.json 4 | tsconfig.test.json 5 | *.zip 6 | *.tar.gz 7 | *.tar 8 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Inspector 2 | 3 | [![CircleCI](https://circleci.com/gh/graphql-hive/graphql-inspector.svg?style=shield&circle-token=d1cd06aba321ee2b7bf8bd2041104643639463b0)](https://circleci.com/gh/graphql-hive/graphql-inspector) 4 | [![npm version](https://badge.fury.io/js/@graphql-inspector/core.svg)](https://npmjs.com/package/@graphql-inspector/core) 5 | 6 | **GraphQL Inspector** outputs a list of changes between two GraphQL schemas. Every change is 7 | precisely explained and marked as breaking, non-breaking or dangerous. It helps you validate 8 | documents and fragments against a schema and even find similar or duplicated types. 9 | 10 | ## Features 11 | 12 | Major features: 13 | 14 | - **Compares schemas** 15 | - **Finds breaking or dangerous changes** 16 | - **Validates documents against a schema** 17 | - **Finds similar / duplicated types** 18 | - **Schema coverage based on documents** 19 | - **Serves a GraphQL server with faked data and GraphQL Playground** 20 | 21 | GraphQL Inspector has a **CLI** and also a **programmatic API**, so you can use it however you want 22 | to and even build tools on top of it. 23 | 24 | ## Installation 25 | 26 | ```bash 27 | pnpm add @graphql-inspector/core 28 | ``` 29 | 30 | ## Examples 31 | 32 | ```typescript 33 | import { 34 | diff, 35 | validate, 36 | similar, 37 | coverage, 38 | Change, 39 | InvalidDocument, 40 | SimilarMap, 41 | SchemaCoverage 42 | } from '@graphql-inspector/core' 43 | 44 | // diff 45 | const changes: Change[] = diff(schemaA, schemaB) 46 | // validate 47 | const invalid: InvalidDocument[] = validate(documentsGlob, schema) 48 | // similar 49 | const similar: SimilarMap = similar(schema, typename, threshold) 50 | // coverage 51 | const schemaCoverage: SchemaCoverage = coverage(schema, documents) 52 | // ... 53 | ``` 54 | 55 | ## License 56 | 57 | [MIT](https://github.com/graphql-hive/graphql-inspector/blob/master/LICENSE) © Kamil Kisiela 58 | -------------------------------------------------------------------------------- /packages/core/__tests__/diff/rules/consider-usage.test.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema } from 'graphql'; 2 | import { considerUsage } from '../../../src/diff/rules/index.js'; 3 | import { CriticalityLevel, diff } from '../../../src/index.js'; 4 | import { findFirstChangeByPath } from '../../../utils/testing.js'; 5 | 6 | describe('considerUsage rule', () => { 7 | test('removed field', async () => { 8 | const a = buildSchema(/* GraphQL */ ` 9 | type Foo { 10 | a: String! 11 | b: String! 12 | } 13 | `); 14 | const b = buildSchema(/* GraphQL */ ` 15 | type Foo { 16 | a: String! 17 | c: String! 18 | } 19 | `); 20 | 21 | const changes = await diff(a, b, [considerUsage], { 22 | async checkUsage(list) { 23 | return list.map(() => true); 24 | }, 25 | }); 26 | 27 | const removed = findFirstChangeByPath(changes, 'Foo.b'); 28 | 29 | expect(removed.criticality.level).toBe(CriticalityLevel.Dangerous); 30 | expect(removed.criticality.isSafeBasedOnUsage).toBe(true); 31 | expect(removed.message).toContain(`non-breaking based on usage`); 32 | }); 33 | 34 | test('removed type', async () => { 35 | const a = buildSchema(/* GraphQL */ ` 36 | type Query { 37 | bar: String! 38 | foo: Foo! 39 | } 40 | 41 | type Foo { 42 | a: String! 43 | } 44 | `); 45 | const b = buildSchema(/* GraphQL */ ` 46 | type Query { 47 | bar: String! 48 | } 49 | `); 50 | 51 | const changes = await diff(a, b, [considerUsage], { 52 | async checkUsage(list) { 53 | return list.map(() => true); 54 | }, 55 | }); 56 | 57 | for (const change of changes) { 58 | expect(change.criticality.level).toBe(CriticalityLevel.Dangerous); 59 | expect(change.criticality.isSafeBasedOnUsage).toBe(true); 60 | expect(change.message).toContain(`non-breaking based on usage`); 61 | } 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/core/__tests__/diff/rules/safe-unreachable.test.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema } from 'graphql'; 2 | import { safeUnreachable } from '../../../src/diff/rules/index.js'; 3 | import { CriticalityLevel, diff } from '../../../src/index.js'; 4 | import { findFirstChangeByPath } from '../../../utils/testing.js'; 5 | 6 | describe('safeUnreachable rule', () => { 7 | test('removed field', async () => { 8 | const a = buildSchema(/* GraphQL */ ` 9 | type Foo { 10 | a: String! 11 | } 12 | 13 | type Bar { 14 | a: String 15 | b: String 16 | } 17 | `); 18 | const b = buildSchema(/* GraphQL */ ` 19 | type Foo { 20 | a: String! 21 | } 22 | 23 | type Bar { 24 | a: String 25 | } 26 | `); 27 | 28 | const changes = await diff(a, b, [safeUnreachable]); 29 | const removed = findFirstChangeByPath(changes, 'Bar.b'); 30 | 31 | expect(removed.criticality.level).toBe(CriticalityLevel.NonBreaking); 32 | expect(removed.message).toContain(`Unreachable from root`); 33 | }); 34 | 35 | test('removed type', async () => { 36 | const a = buildSchema(/* GraphQL */ ` 37 | type Query { 38 | bar: String! 39 | } 40 | 41 | type Foo { 42 | a: String! 43 | } 44 | `); 45 | const b = buildSchema(/* GraphQL */ ` 46 | type Query { 47 | bar: String! 48 | } 49 | `); 50 | 51 | const changes = await diff(a, b, [safeUnreachable]); 52 | const removed = findFirstChangeByPath(changes, 'Foo'); 53 | 54 | expect(removed.criticality.level).toBe(CriticalityLevel.NonBreaking); 55 | expect(removed.message).toContain(`Unreachable from root`); 56 | }); 57 | 58 | test('removed scalar', async () => { 59 | const a = buildSchema(/* GraphQL */ ` 60 | type Query { 61 | bar: String! 62 | } 63 | 64 | scalar JSON 65 | `); 66 | const b = buildSchema(/* GraphQL */ ` 67 | type Query { 68 | bar: String! 69 | } 70 | `); 71 | 72 | const changes = await diff(a, b, [safeUnreachable]); 73 | const removed = findFirstChangeByPath(changes, 'JSON'); 74 | 75 | expect(removed.criticality.level).toBe(CriticalityLevel.NonBreaking); 76 | expect(removed.message).toContain(`Unreachable from root`); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/core/__tests__/utils/compare.test.ts: -------------------------------------------------------------------------------- 1 | import { diffArrays, isEqual, isNotEqual } from '../../src/utils/compare.js'; 2 | 3 | test('isEqual', () => { 4 | expect(isEqual('a', 'a')).toBe(true); 5 | expect(isEqual('a', 'b')).toBe(false); 6 | 7 | expect(isEqual(1, 1)).toBe(true); 8 | expect(isEqual(1, 1.0)).toBe(true); 9 | 10 | expect(isEqual(undefined, 1)).toBe(false); 11 | expect(isEqual(null, 1)).toBe(false); 12 | expect(isEqual(null, null)).toBe(true); 13 | 14 | expect(isEqual(['a'], ['a'])).toBe(true); 15 | expect(isEqual(['a'], ['A'])).toBe(false); 16 | expect(isEqual(['a'], [''])).toBe(false); 17 | expect(isEqual(['a'], ['aa'])).toBe(false); 18 | 19 | expect(isEqual([{ test: 'a' }], [{ test: 'a' }])).toBe(true); 20 | expect(isEqual([{ test: 'a' }], [{ test: 'b' }])).toBe(false); 21 | expect(isEqual([{ test: { deep: 'a' } }], [{ test: { deep: 'a' } }])).toBe(true); 22 | expect(isEqual([{ test: { deep: 'a' } }], [{ test: { deep: 'b' } }])).toBe(false); 23 | expect(isEqual([{ test: { deep: 'a' } }], [{ test: { deeper: 'a' } }])).toBe(false); 24 | expect(isEqual([{ test: { deep: 'a' } }], [{ test: { deep: 'a', twoKeys: 'b' } }])).toBe(false); 25 | expect(isEqual([{ test: { deep: 'a', twoKeys: 'b' } }], [{ test: { deep: 'a' } }])).toBe(false); 26 | }); 27 | 28 | test('isNotEqual', () => { 29 | expect(isNotEqual('a', 'a')).toBe(false); 30 | expect(isNotEqual('a', 'b')).toBe(true); 31 | }); 32 | 33 | test('diffArrays', () => { 34 | expect(diffArrays(['a'], ['a'])).toEqual([]); 35 | expect(diffArrays(['a'], ['a', 'b'])).toEqual([]); 36 | expect(diffArrays(['a', 'b'], ['a'])).toEqual(['b']); 37 | expect(diffArrays(['a', { test: { deep: 'b' } }], [{ test: { deep: 'c' } }])).toEqual([ 38 | 'a', 39 | { test: { deep: 'b' } }, 40 | ]); 41 | expect(diffArrays(['a', { test: { deep: 'b' } }], [{ test: { deep: 'b' } }])).toEqual(['a']); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/core/__tests__/utils/string.test.ts: -------------------------------------------------------------------------------- 1 | import { safeString } from '../../src/utils/string.js'; 2 | 3 | test('scalars', () => { 4 | expect(safeString(0)).toBe('0'); 5 | expect(safeString(42)).toBe('42'); 6 | expect(safeString(42.42)).toBe('42.42'); 7 | expect(safeString('42')).toBe(`"42"`); 8 | expect(safeString('true')).toBe(`"true"`); 9 | expect(safeString(true)).toBe('true'); 10 | expect(safeString('false')).toBe(`"false"`); 11 | expect(safeString(false)).toBe('false'); 12 | }); 13 | 14 | test('null', () => { 15 | expect(safeString(null)).toBe('null'); 16 | }); 17 | 18 | test('undefined', () => { 19 | expect(safeString(undefined)).toBe('undefined'); 20 | }); 21 | 22 | test('object', () => { 23 | expect(safeString({})).toBe('{}'); 24 | expect(safeString(Object.create(null, { foo: { value: 42, enumerable: true } }))).toBe( 25 | '{ foo: 42 }', 26 | ); 27 | }); 28 | 29 | test('array', () => { 30 | expect(safeString(['42', '42'])).toBe("[ '42', '42' ]"); 31 | expect(safeString([{}])).toBe('[ {} ]'); 32 | expect(safeString([Object.create(null, { foo: { value: 42, enumerable: true } })])).toBe( 33 | '[ { foo: 42 } ]', 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/core/__tests__/validate/aws.test.ts: -------------------------------------------------------------------------------- 1 | import { parse, print, Source } from 'graphql'; 2 | import { LoadersRegistry } from '@graphql-inspector/loaders'; 3 | import { validate } from '../../src/index.js'; 4 | 5 | describe('aws', () => { 6 | test('should accept AWS Appsync types', async () => { 7 | const doc = parse(/* GraphQL */ ` 8 | query getPost { 9 | data { 10 | normalScalar 11 | date 12 | time 13 | dateTime 14 | timestamp 15 | email 16 | json 17 | url 18 | phone 19 | ipAddress 20 | } 21 | } 22 | `); 23 | 24 | const schema = await new LoadersRegistry().loadSchema( 25 | /* GraphQL */ ` 26 | type AWS_Data @aws_lambda { 27 | normalScalar: String 28 | date: AWSDate! 29 | time: AWSTime! 30 | dateTime: AWSDateTime! 31 | timestamp: AWSTimestamp! 32 | email: AWSEmail! 33 | json: AWSJSON! 34 | url: AWSURL! 35 | phone: AWSPhone! 36 | ipAddress: AWSIPAddress! 37 | } 38 | 39 | type Query { 40 | data: AWS_Data @aws_lambda 41 | } 42 | `, 43 | {}, 44 | false, 45 | true, 46 | ); 47 | 48 | const results = validate(schema, [new Source(print(doc))]); 49 | 50 | expect(results).toHaveLength(0); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/core/__tests__/validate/token-count.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateTokenCount } from '../../src/index.js'; 2 | 3 | describe('calculateDepth', () => { 4 | it('calculate easy operation', () => { 5 | const source = `{brrt}`; 6 | const count = calculateTokenCount({ 7 | source, 8 | getReferencedFragmentSource: () => { 9 | throw new Error('noop'); 10 | }, 11 | }); 12 | expect(count).toEqual(3); 13 | }); 14 | it('calculate another easy operation', () => { 15 | const source = `query{brrt}`; 16 | const count = calculateTokenCount({ 17 | source, 18 | getReferencedFragmentSource: () => { 19 | throw new Error('noop'); 20 | }, 21 | }); 22 | expect(count).toEqual(4); 23 | }); 24 | it('calculate with external fragment', () => { 25 | const source = `query{...Swag}`; 26 | const count = calculateTokenCount({ 27 | source, 28 | getReferencedFragmentSource: name => { 29 | if (name === 'Swag') { 30 | return `fragment Swag on Query{brrt}`; 31 | } 32 | throw new Error('noop'); 33 | }, 34 | }); 35 | expect(count).toEqual(12); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/core", 3 | "version": "6.2.1", 4 | "type": "module", 5 | "description": "Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/core" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "tools" 58 | ], 59 | "scripts": { 60 | "prepack": "bob prepack" 61 | }, 62 | "peerDependencies": { 63 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 64 | }, 65 | "dependencies": { 66 | "dependency-graph": "1.0.0", 67 | "object-inspect": "1.13.2", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "@types/object-inspect": "1.13.0" 72 | }, 73 | "publishConfig": { 74 | "directory": "dist", 75 | "access": "public" 76 | }, 77 | "sideEffects": false, 78 | "typescript": { 79 | "definition": "dist/typings/index.d.ts" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/core/src/ast/document.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefinitionNode, 3 | FragmentDefinitionNode, 4 | Kind, 5 | OperationDefinitionNode, 6 | parse, 7 | Source, 8 | } from 'graphql'; 9 | 10 | export interface Document { 11 | source: Source; 12 | fragments: { 13 | node: FragmentDefinitionNode; 14 | source: string; 15 | }[]; 16 | operations: { 17 | node: OperationDefinitionNode; 18 | source: string; 19 | }[]; 20 | hasFragments: boolean; 21 | hasOperations: boolean; 22 | } 23 | 24 | export function readDocument(source: Source): Document { 25 | const result: Document = { 26 | source, 27 | fragments: [], 28 | operations: [], 29 | hasFragments: false, 30 | hasOperations: false, 31 | }; 32 | 33 | const documentNode = parse(source.body); 34 | const filepath = source.name; 35 | const definitions = documentNode.definitions || []; 36 | 37 | for (const node of definitions) { 38 | if (isOperation(node)) { 39 | result.operations.push({ 40 | node, 41 | source: filepath, 42 | }); 43 | } else if (isFragment(node)) { 44 | result.fragments.push({ 45 | node, 46 | source: filepath, 47 | }); 48 | } 49 | } 50 | 51 | result.hasFragments = result.fragments.length > 0; 52 | result.hasOperations = result.operations.length > 0; 53 | 54 | return result; 55 | } 56 | 57 | function isOperation(node: DefinitionNode): node is OperationDefinitionNode { 58 | return node.kind === Kind.OPERATION_DEFINITION; 59 | } 60 | 61 | function isFragment(node: DefinitionNode): node is FragmentDefinitionNode { 62 | return node.kind === Kind.FRAGMENT_DEFINITION; 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/src/coverage/output/json.ts: -------------------------------------------------------------------------------- 1 | import { SchemaCoverage } from '../index.js'; 2 | 3 | export function outputJSON(coverage: SchemaCoverage): string { 4 | return JSON.stringify(coverage, null, 2); 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/diff/argument.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLArgument, 3 | GraphQLField, 4 | GraphQLInterfaceType, 5 | GraphQLObjectType, 6 | Kind, 7 | } from 'graphql'; 8 | import { compareLists, diffArrays, isNotEqual } from '../utils/compare.js'; 9 | import { 10 | fieldArgumentDefaultChanged, 11 | fieldArgumentDescriptionChanged, 12 | fieldArgumentTypeChanged, 13 | } from './changes/argument.js'; 14 | import { directiveUsageAdded, directiveUsageRemoved } from './changes/directive-usage.js'; 15 | import { AddChange } from './schema.js'; 16 | 17 | export function changesInArgument( 18 | type: GraphQLObjectType | GraphQLInterfaceType, 19 | field: GraphQLField, 20 | oldArg: GraphQLArgument, 21 | newArg: GraphQLArgument, 22 | addChange: AddChange, 23 | ) { 24 | if (isNotEqual(oldArg.description, newArg.description)) { 25 | addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg)); 26 | } 27 | 28 | if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) { 29 | if (Array.isArray(oldArg.defaultValue) && Array.isArray(newArg.defaultValue)) { 30 | const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue); 31 | if (diff.length > 0) { 32 | addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg)); 33 | } 34 | } else if (JSON.stringify(oldArg.defaultValue) !== JSON.stringify(newArg.defaultValue)) { 35 | addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg)); 36 | } 37 | } 38 | 39 | if (isNotEqual(oldArg.type.toString(), newArg.type.toString())) { 40 | addChange(fieldArgumentTypeChanged(type, field, oldArg, newArg)); 41 | } 42 | 43 | if (oldArg.astNode?.directives && newArg.astNode?.directives) { 44 | compareLists(oldArg.astNode.directives || [], newArg.astNode.directives || [], { 45 | onAdded(directive) { 46 | addChange( 47 | directiveUsageAdded(Kind.ARGUMENT, directive, { 48 | argument: newArg, 49 | field, 50 | type, 51 | }), 52 | ); 53 | }, 54 | 55 | onRemoved(directive) { 56 | addChange( 57 | directiveUsageRemoved(Kind.ARGUMENT, directive, { argument: oldArg, field, type }), 58 | ); 59 | }, 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/src/diff/changes/union.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLUnionType } from 'graphql'; 2 | import { 3 | Change, 4 | ChangeType, 5 | CriticalityLevel, 6 | UnionMemberAddedChange, 7 | UnionMemberRemovedChange, 8 | } from './change.js'; 9 | 10 | function buildUnionMemberRemovedMessage(args: UnionMemberRemovedChange['meta']) { 11 | return `Member '${args.removedUnionMemberTypeName}' was removed from Union type '${args.unionName}'`; 12 | } 13 | 14 | export function unionMemberRemovedFromMeta(args: UnionMemberRemovedChange) { 15 | return { 16 | criticality: { 17 | level: CriticalityLevel.Breaking, 18 | reason: 19 | 'Removing a union member from a union can cause existing queries that use this union member in a fragment spread to error.', 20 | }, 21 | type: ChangeType.UnionMemberRemoved, 22 | message: buildUnionMemberRemovedMessage(args.meta), 23 | meta: args.meta, 24 | path: args.meta.unionName, 25 | } as const; 26 | } 27 | 28 | export function unionMemberRemoved( 29 | union: GraphQLUnionType, 30 | type: GraphQLObjectType, 31 | ): Change { 32 | return unionMemberRemovedFromMeta({ 33 | type: ChangeType.UnionMemberRemoved, 34 | meta: { 35 | unionName: union.name, 36 | removedUnionMemberTypeName: type.name, 37 | }, 38 | }); 39 | } 40 | 41 | function buildUnionMemberAddedMessage(args: UnionMemberAddedChange['meta']) { 42 | return `Member '${args.addedUnionMemberTypeName}' was added to Union type '${args.unionName}'`; 43 | } 44 | 45 | export function buildUnionMemberAddedMessageFromMeta(args: UnionMemberAddedChange) { 46 | return { 47 | criticality: { 48 | level: CriticalityLevel.Dangerous, 49 | reason: 50 | 'Adding a possible type to Unions may break existing clients that were not programming defensively against a new possible type.', 51 | }, 52 | type: ChangeType.UnionMemberAdded, 53 | message: buildUnionMemberAddedMessage(args.meta), 54 | meta: args.meta, 55 | path: args.meta.unionName, 56 | } as const; 57 | } 58 | 59 | export function unionMemberAdded( 60 | union: GraphQLUnionType, 61 | type: GraphQLObjectType, 62 | ): Change { 63 | return buildUnionMemberAddedMessageFromMeta({ 64 | type: ChangeType.UnionMemberAdded, 65 | meta: { 66 | unionName: union.name, 67 | addedUnionMemberTypeName: type.name, 68 | }, 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/src/diff/directive.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLArgument, GraphQLDirective } from 'graphql'; 2 | import { compareLists, diffArrays, isNotEqual } from '../utils/compare.js'; 3 | import { 4 | directiveArgumentAdded, 5 | directiveArgumentDefaultValueChanged, 6 | directiveArgumentDescriptionChanged, 7 | directiveArgumentRemoved, 8 | directiveArgumentTypeChanged, 9 | directiveDescriptionChanged, 10 | directiveLocationAdded, 11 | directiveLocationRemoved, 12 | } from './changes/directive.js'; 13 | import { AddChange } from './schema.js'; 14 | 15 | export function changesInDirective( 16 | oldDirective: GraphQLDirective, 17 | newDirective: GraphQLDirective, 18 | addChange: AddChange, 19 | ) { 20 | if (isNotEqual(oldDirective.description, newDirective.description)) { 21 | addChange(directiveDescriptionChanged(oldDirective, newDirective)); 22 | } 23 | 24 | const locations = { 25 | added: diffArrays(newDirective.locations, oldDirective.locations), 26 | removed: diffArrays(oldDirective.locations, newDirective.locations), 27 | }; 28 | 29 | // locations added 30 | for (const location of locations.added) 31 | addChange(directiveLocationAdded(newDirective, location as any)); 32 | 33 | // locations removed 34 | for (const location of locations.removed) 35 | addChange(directiveLocationRemoved(oldDirective, location as any)); 36 | 37 | compareLists(oldDirective.args, newDirective.args, { 38 | onAdded(arg) { 39 | addChange(directiveArgumentAdded(newDirective, arg)); 40 | }, 41 | onRemoved(arg) { 42 | addChange(directiveArgumentRemoved(oldDirective, arg)); 43 | }, 44 | onMutual(arg) { 45 | changesInDirectiveArgument(oldDirective, arg.oldVersion, arg.newVersion, addChange); 46 | }, 47 | }); 48 | } 49 | 50 | function changesInDirectiveArgument( 51 | directive: GraphQLDirective, 52 | oldArg: GraphQLArgument, 53 | newArg: GraphQLArgument, 54 | addChange: AddChange, 55 | ) { 56 | if (isNotEqual(oldArg.description, newArg.description)) { 57 | addChange(directiveArgumentDescriptionChanged(directive, oldArg, newArg)); 58 | } 59 | 60 | if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) { 61 | addChange(directiveArgumentDefaultValueChanged(directive, oldArg, newArg)); 62 | } 63 | 64 | if (isNotEqual(oldArg.type.toString(), newArg.type.toString())) { 65 | addChange(directiveArgumentTypeChanged(directive, oldArg, newArg)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/core/src/diff/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql'; 2 | import { Change } from './changes/change.js'; 3 | import * as rules from './rules/index.js'; 4 | import { Rule } from './rules/types.js'; 5 | import { diffSchema } from './schema.js'; 6 | 7 | export * from './rules/types.js'; 8 | export const DiffRule = rules; 9 | 10 | export * from './onComplete/types.js'; 11 | export type { UsageHandler } from './rules/consider-usage.js'; 12 | 13 | export function diff( 14 | oldSchema: GraphQLSchema, 15 | newSchema: GraphQLSchema, 16 | rules: Rule[] = [], 17 | config?: rules.ConsiderUsageConfig, 18 | ): Promise { 19 | const changes = diffSchema(oldSchema, newSchema); 20 | 21 | return rules.reduce(async (prev, rule) => { 22 | const prevChanges = await prev; 23 | return rule({ 24 | changes: prevChanges, 25 | oldSchema, 26 | newSchema, 27 | config, 28 | }); 29 | }, Promise.resolve(changes)); 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/diff/interface.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLInterfaceType, Kind } from 'graphql'; 2 | import { compareLists } from '../utils/compare.js'; 3 | import { directiveUsageAdded, directiveUsageRemoved } from './changes/directive-usage.js'; 4 | import { fieldAdded, fieldRemoved } from './changes/field.js'; 5 | import { changesInField } from './field.js'; 6 | import { AddChange } from './schema.js'; 7 | 8 | export function changesInInterface( 9 | oldInterface: GraphQLInterfaceType, 10 | newInterface: GraphQLInterfaceType, 11 | addChange: AddChange, 12 | ) { 13 | compareLists(Object.values(oldInterface.getFields()), Object.values(newInterface.getFields()), { 14 | onAdded(field) { 15 | addChange(fieldAdded(newInterface, field)); 16 | }, 17 | onRemoved(field) { 18 | addChange(fieldRemoved(oldInterface, field)); 19 | }, 20 | onMutual(field) { 21 | changesInField(oldInterface, field.oldVersion, field.newVersion, addChange); 22 | }, 23 | }); 24 | compareLists(oldInterface.astNode?.directives || [], newInterface.astNode?.directives || [], { 25 | onAdded(directive) { 26 | addChange(directiveUsageAdded(Kind.INTERFACE_TYPE_DEFINITION, directive, newInterface)); 27 | }, 28 | onRemoved(directive) { 29 | addChange(directiveUsageRemoved(Kind.INTERFACE_TYPE_DEFINITION, directive, oldInterface)); 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/diff/object.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, Kind } from 'graphql'; 2 | import { compareLists } from '../utils/compare.js'; 3 | import { directiveUsageAdded, directiveUsageRemoved } from './changes/directive-usage.js'; 4 | import { fieldAdded, fieldRemoved } from './changes/field.js'; 5 | import { objectTypeInterfaceAdded, objectTypeInterfaceRemoved } from './changes/object.js'; 6 | import { changesInField } from './field.js'; 7 | import { AddChange } from './schema.js'; 8 | 9 | export function changesInObject( 10 | oldType: GraphQLObjectType, 11 | newType: GraphQLObjectType, 12 | addChange: AddChange, 13 | ) { 14 | const oldInterfaces = oldType.getInterfaces(); 15 | const newInterfaces = newType.getInterfaces(); 16 | 17 | const oldFields = oldType.getFields(); 18 | const newFields = newType.getFields(); 19 | 20 | compareLists(oldInterfaces, newInterfaces, { 21 | onAdded(i) { 22 | addChange(objectTypeInterfaceAdded(i, newType)); 23 | }, 24 | onRemoved(i) { 25 | addChange(objectTypeInterfaceRemoved(i, oldType)); 26 | }, 27 | }); 28 | 29 | compareLists(Object.values(oldFields), Object.values(newFields), { 30 | onAdded(f) { 31 | addChange(fieldAdded(newType, f)); 32 | }, 33 | onRemoved(f) { 34 | addChange(fieldRemoved(oldType, f)); 35 | }, 36 | onMutual(f) { 37 | changesInField(oldType, f.oldVersion, f.newVersion, addChange); 38 | }, 39 | }); 40 | 41 | compareLists(oldType.astNode?.directives || [], newType.astNode?.directives || [], { 42 | onAdded(directive) { 43 | addChange(directiveUsageAdded(Kind.OBJECT, directive, newType)); 44 | }, 45 | onRemoved(directive) { 46 | addChange(directiveUsageRemoved(Kind.OBJECT, directive, oldType)); 47 | }, 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/diff/onComplete/types.ts: -------------------------------------------------------------------------------- 1 | import { Change } from '../changes/change.js'; 2 | 3 | export type CompletionArgs = { 4 | breakingChanges: Change[]; 5 | dangerousChanges: Change[]; 6 | nonBreakingChanges: Change[]; 7 | }; 8 | 9 | export type CompletionHandler = (args: CompletionArgs) => void; 10 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/config.ts: -------------------------------------------------------------------------------- 1 | import { ConsiderUsageConfig } from './consider-usage.js'; 2 | 3 | export type Config = ConsiderUsageConfig; 4 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/dangerous-breaking.ts: -------------------------------------------------------------------------------- 1 | import { CriticalityLevel } from '../changes/change.js'; 2 | import { Rule } from './types.js'; 3 | 4 | export const dangerousBreaking: Rule = ({ changes }) => { 5 | return changes.map(change => { 6 | if (change.criticality.level === CriticalityLevel.Dangerous) { 7 | return { 8 | ...change, 9 | criticality: { 10 | ...change.criticality, 11 | level: CriticalityLevel.Breaking, 12 | }, 13 | }; 14 | } 15 | 16 | return change; 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/ignore-description-changes.ts: -------------------------------------------------------------------------------- 1 | import { ChangeType, TypeOfChangeType } from '../changes/change.js'; 2 | import { Rule } from './types.js'; 3 | 4 | const descriptionChangeTypes: TypeOfChangeType[] = [ 5 | ChangeType.FieldArgumentDescriptionChanged, 6 | ChangeType.DirectiveDescriptionChanged, 7 | ChangeType.DirectiveArgumentDescriptionChanged, 8 | ChangeType.EnumValueDescriptionChanged, 9 | ChangeType.FieldDescriptionChanged, 10 | ChangeType.FieldDescriptionAdded, 11 | ChangeType.FieldDescriptionRemoved, 12 | ChangeType.InputFieldDescriptionAdded, 13 | ChangeType.InputFieldDescriptionRemoved, 14 | ChangeType.InputFieldDescriptionChanged, 15 | ChangeType.TypeDescriptionChanged, 16 | ]; 17 | 18 | export const ignoreDescriptionChanges: Rule = ({ changes }) => { 19 | return changes.filter(change => !descriptionChangeTypes.includes(change.type)); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/ignore-usage-directives.ts: -------------------------------------------------------------------------------- 1 | import { ChangeType } from '../changes/change.js'; 2 | import { Rule } from './types.js'; 3 | 4 | interface IgnoreDirectivesConfig { 5 | ignoredDirectives: string[]; 6 | } 7 | 8 | export const ignoreDirectives: Rule = ({ changes, config }) => { 9 | if (!config?.ignoredDirectives?.length) { 10 | return changes; 11 | } 12 | const ignoredDirectiveSet = new Set(config.ignoredDirectives); 13 | 14 | const filteredChanges = changes.filter(change => { 15 | if (change.type === ChangeType && change.path) { 16 | const directiveName = change.path.split('.')[1]; 17 | return !ignoredDirectiveSet.has(directiveName); 18 | } 19 | 20 | return true; 21 | }); 22 | 23 | return filteredChanges; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './consider-usage.js'; 2 | export * from './dangerous-breaking.js'; 3 | export * from './ignore-description-changes.js'; 4 | export * from './safe-unreachable.js'; 5 | export * from './suppress-removal-of-deprecated-field.js'; 6 | export * from './ignore-usage-directives.js'; 7 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/safe-unreachable.ts: -------------------------------------------------------------------------------- 1 | import { getReachableTypes } from '../../utils/graphql.js'; 2 | import { parsePath } from '../../utils/path.js'; 3 | import { CriticalityLevel } from '../changes/change.js'; 4 | import { Rule } from './types.js'; 5 | 6 | export const safeUnreachable: Rule = ({ changes, oldSchema }) => { 7 | const reachable = getReachableTypes(oldSchema); 8 | 9 | return changes.map(change => { 10 | if (change.criticality.level === CriticalityLevel.Breaking && change.path) { 11 | const [typeName] = parsePath(change.path); 12 | 13 | if (!reachable.has(typeName)) { 14 | return { 15 | ...change, 16 | criticality: { 17 | ...change.criticality, 18 | level: CriticalityLevel.NonBreaking, 19 | }, 20 | message: 'Unreachable from root', 21 | }; 22 | } 23 | } 24 | 25 | return change; 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/core/src/diff/rules/types.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql'; 2 | import { Change } from '../changes/change.js'; 3 | 4 | export type Rule = (input: { 5 | changes: Change[]; 6 | oldSchema: GraphQLSchema; 7 | newSchema: GraphQLSchema; 8 | config: TConfig; 9 | }) => Change[] | Promise; 10 | -------------------------------------------------------------------------------- /packages/core/src/diff/scalar.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType, Kind } from 'graphql'; 2 | import { compareLists } from '../utils/compare.js'; 3 | import { directiveUsageAdded, directiveUsageRemoved } from './changes/directive-usage.js'; 4 | import { AddChange } from './schema.js'; 5 | 6 | export function changesInScalar( 7 | oldScalar: GraphQLScalarType, 8 | newScalar: GraphQLScalarType, 9 | addChange: AddChange, 10 | ) { 11 | compareLists(oldScalar.astNode?.directives || [], newScalar.astNode?.directives || [], { 12 | onAdded(directive) { 13 | addChange(directiveUsageAdded(Kind.SCALAR_TYPE_DEFINITION, directive, newScalar)); 14 | }, 15 | onRemoved(directive) { 16 | addChange(directiveUsageRemoved(Kind.SCALAR_TYPE_DEFINITION, directive, oldScalar)); 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/diff/union.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLUnionType, Kind } from 'graphql'; 2 | import { compareLists } from '../utils/compare.js'; 3 | import { directiveUsageAdded, directiveUsageRemoved } from './changes/directive-usage.js'; 4 | import { unionMemberAdded, unionMemberRemoved } from './changes/union.js'; 5 | import { AddChange } from './schema.js'; 6 | 7 | export function changesInUnion( 8 | oldUnion: GraphQLUnionType, 9 | newUnion: GraphQLUnionType, 10 | addChange: AddChange, 11 | ) { 12 | const oldTypes = oldUnion.getTypes(); 13 | const newTypes = newUnion.getTypes(); 14 | 15 | compareLists(oldTypes, newTypes, { 16 | onAdded(t) { 17 | addChange(unionMemberAdded(newUnion, t)); 18 | }, 19 | onRemoved(t) { 20 | addChange(unionMemberRemoved(oldUnion, t)); 21 | }, 22 | }); 23 | 24 | compareLists(oldUnion.astNode?.directives || [], newUnion.astNode?.directives || [], { 25 | onAdded(directive) { 26 | addChange(directiveUsageAdded(Kind.UNION_TYPE_DEFINITION, directive, newUnion)); 27 | }, 28 | onRemoved(directive) { 29 | addChange(directiveUsageRemoved(Kind.UNION_TYPE_DEFINITION, directive, oldUnion)); 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/similar/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLNamedType, GraphQLSchema, printType } from 'graphql'; 2 | import { isForIntrospection, isPrimitive } from '../utils/graphql.js'; 3 | import { BestMatch, findBestMatch, Rating, Target } from '../utils/string.js'; 4 | 5 | export interface SimilarMap { 6 | [name: string]: BestMatch; 7 | } 8 | 9 | export function similar( 10 | schema: GraphQLSchema, 11 | typeName: string | undefined, 12 | threshold = 0.4, 13 | ): SimilarMap { 14 | const typeMap = schema.getTypeMap(); 15 | const targets: Target[] = Object.keys(schema.getTypeMap()) 16 | .filter(name => !isPrimitive(name) && !isForIntrospection(name)) 17 | .map(name => ({ 18 | typeId: name, 19 | value: stripType(typeMap[name]), 20 | })); 21 | const results: SimilarMap = {}; 22 | 23 | if (typeof typeName !== 'undefined' && !targets.some(t => t.typeId === typeName)) { 24 | throw new Error(`Type '${typeName}' doesn't exist`); 25 | } 26 | 27 | for (const source of typeName ? [{ typeId: typeName, value: '' }] : targets) { 28 | const sourceType = schema.getType(source.typeId) as GraphQLNamedType; 29 | const matchWith = targets.filter( 30 | target => 31 | (schema.getType(target.typeId) as any).astNode.kind === (sourceType.astNode as any).kind && 32 | target.typeId !== source.typeId, 33 | ); 34 | 35 | if (matchWith.length > 0) { 36 | const found = similarTo(sourceType, matchWith, threshold); 37 | 38 | if (found) { 39 | results[source.typeId] = found; 40 | } 41 | } 42 | } 43 | 44 | return results; 45 | } 46 | 47 | function similarTo( 48 | type: GraphQLNamedType, 49 | targets: Target[], 50 | threshold: number, 51 | ): BestMatch | undefined { 52 | const types = targets.filter(target => target.typeId !== type.name); 53 | const result = findBestMatch(stripType(type), types); 54 | 55 | if (result.bestMatch.rating < threshold) { 56 | return; 57 | } 58 | 59 | return { 60 | bestMatch: result.bestMatch, 61 | ratings: result.ratings 62 | .filter(r => r.rating >= threshold && r.target !== result.bestMatch.target) 63 | .sort((a: Rating, b: Rating) => a.rating - b.rating) 64 | .reverse(), 65 | }; 66 | } 67 | 68 | function stripType(type: GraphQLNamedType): string { 69 | return printType(type) 70 | .trim() 71 | .replace(/^[a-z]+ [^{]+\{/g, '') 72 | .replace(/\}$/g, '') 73 | .trim() 74 | .split('\n') 75 | .map(s => s.trim()) 76 | .sort((a, b) => a.localeCompare(b)) 77 | .join(' '); 78 | } 79 | -------------------------------------------------------------------------------- /packages/core/src/utils/apollo.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, extendSchema, GraphQLSchema, parse, visit } from 'graphql'; 2 | import { removeDirectives, removeFieldIfDirectives } from './graphql.js'; 3 | 4 | export function transformDocumentWithApollo( 5 | doc: DocumentNode, 6 | { keepClientFields }: { keepClientFields: boolean }, 7 | ): DocumentNode { 8 | return visit(doc, { 9 | Field(node) { 10 | return keepClientFields 11 | ? removeDirectives(node, ['client']) 12 | : removeFieldIfDirectives(node, ['client']); 13 | }, 14 | }); 15 | } 16 | 17 | export function transformSchemaWithApollo(schema: GraphQLSchema): GraphQLSchema { 18 | return extendSchema( 19 | schema, 20 | parse(/* GraphQL */ ` 21 | directive @connection(key: String!, filter: [String]) on FIELD 22 | `), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/utils/is-deprecated.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLEnumValue, GraphQLField, GraphQLInputField } from 'graphql'; 2 | 3 | export function isDeprecated( 4 | fieldOrEnumValue: GraphQLField | GraphQLEnumValue | GraphQLInputField, 5 | ): boolean { 6 | if ('isDeprecated' in fieldOrEnumValue) { 7 | return !!fieldOrEnumValue['isDeprecated']; 8 | } 9 | if (fieldOrEnumValue.deprecationReason != null) { 10 | return true; 11 | } 12 | if ( 13 | fieldOrEnumValue.astNode?.directives?.some(directive => directive.name.value === 'deprecated') 14 | ) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/utils/path.ts: -------------------------------------------------------------------------------- 1 | export function parsePath(path: string): string[] { 2 | return path.split('.'); 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/src/validate/alias-count.ts: -------------------------------------------------------------------------------- 1 | import { DepGraph } from 'dependency-graph'; 2 | import type { 3 | DocumentNode, 4 | FieldNode, 5 | FragmentDefinitionNode, 6 | FragmentSpreadNode, 7 | InlineFragmentNode, 8 | OperationDefinitionNode, 9 | Source, 10 | } from 'graphql'; 11 | import { GraphQLError, Kind } from 'graphql'; 12 | 13 | export function validateAliasCount({ 14 | source, 15 | doc, 16 | maxAliasCount, 17 | fragmentGraph, 18 | }: { 19 | source: Source; 20 | doc: DocumentNode; 21 | maxAliasCount: number; 22 | fragmentGraph: DepGraph; 23 | }): GraphQLError | void { 24 | const getFragmentByFragmentName = (fragmentName: string) => 25 | fragmentGraph.getNodeData(fragmentName); 26 | 27 | for (const definition of doc.definitions) { 28 | if (definition.kind !== Kind.OPERATION_DEFINITION) { 29 | continue; 30 | } 31 | const aliasCount = countAliases(definition, getFragmentByFragmentName); 32 | if (aliasCount > maxAliasCount) { 33 | return new GraphQLError( 34 | `Too many aliases (${aliasCount}). Maximum allowed is ${maxAliasCount}`, 35 | [definition], 36 | source, 37 | definition.loc?.start ? [definition.loc.start] : undefined, 38 | ); 39 | } 40 | } 41 | } 42 | 43 | export function countAliases( 44 | node: 45 | | FieldNode 46 | | FragmentDefinitionNode 47 | | InlineFragmentNode 48 | | OperationDefinitionNode 49 | | FragmentSpreadNode, 50 | getFragmentByName: (fragmentName: string) => FragmentDefinitionNode | undefined, 51 | ) { 52 | let aliases = 0; 53 | if ('alias' in node && node.alias) { 54 | ++aliases; 55 | } 56 | if ('selectionSet' in node && node.selectionSet) { 57 | for (const child of node.selectionSet.selections) { 58 | aliases += countAliases(child, getFragmentByName); 59 | } 60 | } else if (node.kind === Kind.FRAGMENT_SPREAD) { 61 | const fragmentNode = getFragmentByName(node.name.value); 62 | if (fragmentNode) { 63 | aliases += countAliases(fragmentNode, getFragmentByName); 64 | } 65 | } 66 | return aliases; 67 | } 68 | -------------------------------------------------------------------------------- /packages/core/src/validate/directive-count.ts: -------------------------------------------------------------------------------- 1 | import { DepGraph } from 'dependency-graph'; 2 | import type { 3 | DocumentNode, 4 | FieldNode, 5 | FragmentDefinitionNode, 6 | FragmentSpreadNode, 7 | InlineFragmentNode, 8 | OperationDefinitionNode, 9 | Source, 10 | } from 'graphql'; 11 | import { GraphQLError, Kind } from 'graphql'; 12 | 13 | export function validateDirectiveCount({ 14 | source, 15 | doc, 16 | maxDirectiveCount, 17 | fragmentGraph, 18 | }: { 19 | source: Source; 20 | doc: DocumentNode; 21 | maxDirectiveCount: number; 22 | fragmentGraph: DepGraph; 23 | }): GraphQLError | void { 24 | const getFragmentByFragmentName = (fragmentName: string) => 25 | fragmentGraph.getNodeData(fragmentName); 26 | 27 | for (const definition of doc.definitions) { 28 | if (definition.kind !== Kind.OPERATION_DEFINITION) { 29 | continue; 30 | } 31 | const directiveCount = countDirectives(definition, getFragmentByFragmentName); 32 | if (directiveCount > maxDirectiveCount) { 33 | return new GraphQLError( 34 | `Too many directives (${directiveCount}). Maximum allowed is ${maxDirectiveCount}`, 35 | [definition], 36 | source, 37 | definition.loc?.start ? [definition.loc.start] : undefined, 38 | ); 39 | } 40 | } 41 | } 42 | 43 | export function countDirectives( 44 | node: 45 | | FieldNode 46 | | FragmentDefinitionNode 47 | | InlineFragmentNode 48 | | OperationDefinitionNode 49 | | FragmentSpreadNode, 50 | getFragmentByName: (fragmentName: string) => FragmentDefinitionNode | undefined, 51 | ) { 52 | let directives = 0; 53 | if (node.directives) { 54 | directives += node.directives.length; 55 | } 56 | if ('selectionSet' in node && node.selectionSet) { 57 | for (const child of node.selectionSet.selections) { 58 | directives += countDirectives(child, getFragmentByName); 59 | } 60 | } 61 | if (node.kind === Kind.FRAGMENT_SPREAD) { 62 | const fragment = getFragmentByName(node.name.value); 63 | if (fragment) { 64 | directives += countDirectives(fragment, getFragmentByName); 65 | } 66 | } 67 | return directives; 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/src/validate/token-count.ts: -------------------------------------------------------------------------------- 1 | import type { ParseOptions, Source } from 'graphql'; 2 | import { DocumentNode, GraphQLError, TokenKind, visit } from 'graphql'; 3 | import { Parser } from 'graphql/language/parser.js'; 4 | 5 | class ParserWithLexer extends Parser { 6 | private __tokenCount = 0; 7 | 8 | get tokenCount() { 9 | return this.__tokenCount; 10 | } 11 | 12 | constructor(source: string | Source, options?: ParseOptions) { 13 | super(source, options); 14 | const lexer = this._lexer; 15 | this._lexer = new Proxy(lexer, { 16 | get: (target, prop, receiver) => { 17 | if (prop === 'advance') { 18 | return () => { 19 | const token = target.advance(); 20 | if (token.kind !== TokenKind.EOF) { 21 | this.__tokenCount++; 22 | } 23 | return token; 24 | }; 25 | } 26 | return Reflect.get(target, prop, receiver); 27 | }, 28 | }); 29 | } 30 | } 31 | 32 | export function calculateTokenCount(args: { 33 | source: Source | string; 34 | getReferencedFragmentSource: (fragmentName: string) => Source | string | undefined; 35 | }): number { 36 | const parser = new ParserWithLexer(args.source); 37 | const document = parser.parseDocument(); 38 | let { tokenCount } = parser; 39 | 40 | visit(document, { 41 | FragmentSpread(node) { 42 | const fragmentSource = args.getReferencedFragmentSource(node.name.value); 43 | if (fragmentSource) { 44 | tokenCount += calculateTokenCount({ 45 | source: fragmentSource, 46 | getReferencedFragmentSource: args.getReferencedFragmentSource, 47 | }); 48 | } 49 | }, 50 | }); 51 | 52 | return tokenCount; 53 | } 54 | 55 | export function validateTokenCount(args: { 56 | source: Source; 57 | document: DocumentNode; 58 | getReferencedFragmentSource: (fragmentName: string) => Source | string | undefined; 59 | maxTokenCount: number; 60 | }): GraphQLError | void { 61 | const tokenCount = calculateTokenCount(args); 62 | if (tokenCount > args.maxTokenCount) { 63 | return new GraphQLError( 64 | `Query exceeds maximum token count of ${args.maxTokenCount} (actual: ${tokenCount})`, 65 | args.document, 66 | args.source, 67 | args.document.loc?.start ? [args.document.loc.start] : undefined, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/utils/testing.ts: -------------------------------------------------------------------------------- 1 | import { Change } from '../src/diff/changes/change.js'; 2 | 3 | export function findChangesByPath(changes: Change[], path: string) { 4 | return changes.filter(c => c.path === path); 5 | } 6 | 7 | export function findFirstChangeByPath(changes: Change[], path: string) { 8 | return findChangesByPath(changes, path)[0]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/loaders/code/__tests__/assets/bar.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const typeDefs = gql` 4 | type Mutation { 5 | bar: String 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /packages/loaders/code/__tests__/code.test.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, GraphQLObjectType } from 'graphql'; 2 | import loader from '../src/index.js'; 3 | 4 | test('should contain descriptions (#1604)', async () => { 5 | const results = await loader.load('./assets/bar.ts', { 6 | cwd: __dirname, 7 | }); 8 | const mutation = buildSchema(results[0].rawSDL!).getType('Mutation') as GraphQLObjectType; 9 | 10 | expect(mutation.getFields().bar).toBeDefined(); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/loaders/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/code-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema and Documents from TypeScript and JavaScript code", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/code" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/code-file-loader": "8.1.20", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "@babel/core": "7.26.10", 72 | "graphql": "16.9.0" 73 | }, 74 | "publishConfig": { 75 | "directory": "dist", 76 | "access": "public" 77 | }, 78 | "sideEffects": false, 79 | "typescript": { 80 | "definition": "dist/typings/index.d.ts" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/loaders/code/src/index.ts: -------------------------------------------------------------------------------- 1 | import { CodeFileLoader } from '@graphql-tools/code-file-loader'; 2 | 3 | export default new CodeFileLoader(); 4 | -------------------------------------------------------------------------------- /packages/loaders/git/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/git-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema and Documents from Git repository", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/git" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/git-loader": "8.0.24", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "@babel/core": "7.26.10", 72 | "graphql": "16.9.0" 73 | }, 74 | "publishConfig": { 75 | "directory": "dist", 76 | "access": "public" 77 | }, 78 | "sideEffects": false, 79 | "typescript": { 80 | "definition": "dist/typings/index.d.ts" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/loaders/git/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GitLoader } from '@graphql-tools/git-loader'; 2 | 3 | export default new GitLoader(); 4 | -------------------------------------------------------------------------------- /packages/loaders/github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/github-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema and Documents from GitHub repository", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/github" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/github-loader": "8.0.19", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "@babel/core": "7.26.10", 72 | "@types/node": "20.14.15", 73 | "graphql": "16.9.0" 74 | }, 75 | "publishConfig": { 76 | "directory": "dist", 77 | "access": "public" 78 | }, 79 | "sideEffects": false, 80 | "typescript": { 81 | "definition": "dist/typings/index.d.ts" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/loaders/github/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GithubLoader } from '@graphql-tools/github-loader'; 2 | 3 | export default new GithubLoader(); 4 | -------------------------------------------------------------------------------- /packages/loaders/graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/graphql-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema and Documents from GraphQL files (.graphql, .gql etc)", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/graphql" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/graphql-file-loader": "8.0.19", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "graphql": "16.9.0" 72 | }, 73 | "publishConfig": { 74 | "directory": "dist", 75 | "access": "public" 76 | }, 77 | "sideEffects": false, 78 | "typescript": { 79 | "definition": "dist/typings/index.d.ts" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/loaders/graphql/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; 2 | 3 | export default new GraphQLFileLoader(); 4 | -------------------------------------------------------------------------------- /packages/loaders/json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/json-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema from JSON files (introspection result)", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/json" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/json-file-loader": "8.0.18", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "graphql": "16.9.0" 72 | }, 73 | "publishConfig": { 74 | "directory": "dist", 75 | "access": "public" 76 | }, 77 | "sideEffects": false, 78 | "typescript": { 79 | "definition": "dist/typings/index.d.ts" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/loaders/json/src/index.ts: -------------------------------------------------------------------------------- 1 | import { JsonFileLoader } from '@graphql-tools/json-file-loader'; 2 | 3 | export default new JsonFileLoader(); 4 | -------------------------------------------------------------------------------- /packages/loaders/loaders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/loaders", 3 | "version": "4.0.5", 4 | "type": "module", 5 | "description": "Loaders in GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/plugin" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "@graphql-inspector/config": "^4.0.0", 65 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 66 | }, 67 | "dependencies": { 68 | "@graphql-tools/code-file-loader": "8.1.20", 69 | "@graphql-tools/load": "8.0.19", 70 | "@graphql-tools/utils": "10.8.6", 71 | "tslib": "2.6.2" 72 | }, 73 | "devDependencies": { 74 | "@babel/core": "7.26.10", 75 | "graphql": "16.9.0" 76 | }, 77 | "publishConfig": { 78 | "directory": "dist", 79 | "access": "public" 80 | }, 81 | "sideEffects": false, 82 | "typescript": { 83 | "definition": "dist/typings/index.d.ts" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/loaders/url/__tests__/introspection.test.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, GraphQLObjectType } from 'graphql'; 2 | import { mockGraphQLServer } from '@graphql-inspector/testing'; 3 | import loader from '../src/index.js'; 4 | 5 | test('should contain descriptions', async () => { 6 | const schema = buildSchema(/* GraphQL */ ` 7 | """ 8 | User type it is 9 | """ 10 | type User { 11 | """ 12 | User of ID, of course 13 | """ 14 | id: ID! 15 | } 16 | 17 | type Query { 18 | """ 19 | Get User by ID 20 | """ 21 | user(id: ID!): User 22 | } 23 | `); 24 | 25 | const done = mockGraphQLServer({ 26 | schema, 27 | host: 'http://localhost', 28 | path: '/graphql', 29 | }); 30 | 31 | const introspectedSchema = await loader.load('http://localhost/graphql', {}); 32 | 33 | const user = introspectedSchema[0].schema.getType('User') as GraphQLObjectType; 34 | 35 | // User 36 | expect(user.description).toBe('User type it is'); 37 | // User.id 38 | expect(user.getFields().id.description).toBe('User of ID, of course'); 39 | // Query.user 40 | expect(schema.getQueryType().getFields().user.description).toBe('Get User by ID'); 41 | 42 | done(); 43 | }); 44 | 45 | test('use GET method', async () => { 46 | const schema = buildSchema(/* GraphQL */ ` 47 | """ 48 | User type it is 49 | """ 50 | type User { 51 | """ 52 | User of ID, of course 53 | """ 54 | id: ID! 55 | } 56 | 57 | type Query { 58 | """ 59 | Get User by ID 60 | """ 61 | user(id: ID!): User 62 | } 63 | `); 64 | 65 | const done = mockGraphQLServer({ 66 | schema, 67 | host: 'http://localhost', 68 | path: '/graphql', 69 | method: 'GET', 70 | }); 71 | 72 | const introspectedSchema = await loader.load('http://localhost/graphql', { 73 | method: 'GET', 74 | }); 75 | 76 | const user = introspectedSchema[0].schema.getType('User') as GraphQLObjectType; 77 | 78 | // User 79 | expect(user.description).toBe('User type it is'); 80 | // User.id 81 | expect(user.getFields().id.description).toBe('User of ID, of course'); 82 | // Query.user 83 | expect(schema.getQueryType().getFields().user.description).toBe('Get User by ID'); 84 | 85 | done(); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/loaders/url/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/url-loader", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Load GraphQL Schema from GraphQL endpoint (introspection)", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/plugins/loaders/url" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "keywords": [ 55 | "graphql", 56 | "graphql-inspector", 57 | "graphql-inspector-plugin", 58 | "tools" 59 | ], 60 | "scripts": { 61 | "prepack": "bob prepack" 62 | }, 63 | "peerDependencies": { 64 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 65 | }, 66 | "dependencies": { 67 | "@graphql-tools/url-loader": "8.0.31", 68 | "tslib": "2.6.2" 69 | }, 70 | "devDependencies": { 71 | "@types/node": "20.14.15", 72 | "graphql": "16.9.0" 73 | }, 74 | "publishConfig": { 75 | "directory": "dist", 76 | "access": "public" 77 | }, 78 | "sideEffects": false, 79 | "typescript": { 80 | "definition": "dist/typings/index.d.ts" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/loaders/url/src/index.ts: -------------------------------------------------------------------------------- 1 | import { UrlLoader } from '@graphql-tools/url-loader'; 2 | 3 | export default new UrlLoader(); 4 | -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphql-inspector/logger", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "description": "Logger for GraphQL Inspector", 6 | "repository": { 7 | "type": "git", 8 | "url": "graphql-hive/graphql-inspector", 9 | "directory": "packages/logger" 10 | }, 11 | "author": { 12 | "name": "Kamil Kisiela", 13 | "email": "kamil.kisiela@gmail.com", 14 | "url": "https://github.com/kamilkisiela" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "exports": { 23 | ".": { 24 | "require": { 25 | "types": "./dist/typings/index.d.cts", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "import": { 29 | "types": "./dist/typings/index.d.ts", 30 | "default": "./dist/esm/index.js" 31 | }, 32 | "default": { 33 | "types": "./dist/typings/index.d.ts", 34 | "default": "./dist/esm/index.js" 35 | } 36 | }, 37 | "./*": { 38 | "require": { 39 | "types": "./dist/typings/*.d.cts", 40 | "default": "./dist/cjs/*.js" 41 | }, 42 | "import": { 43 | "types": "./dist/typings/*.d.ts", 44 | "default": "./dist/esm/*.js" 45 | }, 46 | "default": { 47 | "types": "./dist/typings/*.d.ts", 48 | "default": "./dist/esm/*.js" 49 | } 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typings": "dist/typings/index.d.ts", 54 | "scripts": { 55 | "prepack": "bob prepack" 56 | }, 57 | "dependencies": { 58 | "chalk": "4.1.2", 59 | "figures": "3.2.0", 60 | "log-symbols": "4.1.0", 61 | "std-env": "3.7.0", 62 | "tslib": "2.6.2" 63 | }, 64 | "publishConfig": { 65 | "directory": "dist", 66 | "access": "public" 67 | }, 68 | "sideEffects": false, 69 | "typescript": { 70 | "definition": "dist/typings/index.d.ts" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/logger/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'std-env'; 2 | -------------------------------------------------------------------------------- /packages/testing/src/index.ts: -------------------------------------------------------------------------------- 1 | import { execute, GraphQLSchema, parse } from 'graphql'; 2 | import nock from 'nock'; 3 | 4 | export function mockGraphQLServer({ 5 | schema, 6 | host, 7 | path, 8 | method = 'POST', 9 | }: { 10 | schema: GraphQLSchema; 11 | host: string; 12 | path: string; 13 | method?: 'GET' | 'POST'; 14 | }) { 15 | const scope = nock(host); 16 | if (method === 'GET') { 17 | scope 18 | .get(path => path.startsWith(path)) 19 | .reply(async (unformattedQuery, _: any) => { 20 | const query = new URL(host + unformattedQuery).searchParams.get('query'); 21 | try { 22 | const result = await execute({ 23 | schema, 24 | document: parse(query || ''), 25 | }); 26 | return [200, result]; 27 | } catch (error) { 28 | return [500, error]; 29 | } 30 | }); 31 | } else { 32 | scope.post(path).reply(async (_, body: any) => { 33 | try { 34 | const result = await execute({ 35 | schema, 36 | document: parse(body.query), 37 | operationName: body.operationName, 38 | variableValues: body.variables, 39 | }); 40 | return [200, result]; 41 | } catch (error) { 42 | return [500, error]; 43 | } 44 | }); 45 | } 46 | 47 | return () => { 48 | scope.done(); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /packages/testing/src/setup-file.ts: -------------------------------------------------------------------------------- 1 | import jsesc from 'jsesc'; 2 | import stripAnsi from 'strip-ansi'; 3 | import type { MockInstance } from 'vitest'; 4 | 5 | function nonTTY(msg: string) { 6 | return stripAnsi(jsesc(stripAnsi(msg))); 7 | } 8 | 9 | expect.extend({ 10 | toHaveBeenCalledNormalized(spy: MockInstance, expected: string) { 11 | const normalizedExpected = nonTTY(expected); 12 | const calls = spy.mock.calls; 13 | const contain = calls.some(args => nonTTY(args.join(' ')).includes(normalizedExpected)); 14 | 15 | if (contain) { 16 | return { 17 | message: () => `expected not to be a called with ${expected}`, 18 | pass: true, 19 | }; 20 | } 21 | const message = `expected to be called with ${expected}`; 22 | 23 | return { 24 | message: () => message, 25 | pass: false, 26 | }; 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/testing/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jsesc'; 2 | declare module 'strip-ansi'; 3 | 4 | interface CustomMatchers { 5 | toHaveBeenCalledNormalized(expected: string): R; 6 | } 7 | 8 | declare global { 9 | namespace Vi { 10 | interface Assertion extends CustomMatchers {} 11 | 12 | interface AsymmetricMatchersContaining extends CustomMatchers {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - packages/commands/* 4 | - packages/loaders/* 5 | - website 6 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-require-imports 2 | const config = require('@theguild/prettier-config'); 3 | 4 | module.exports = { 5 | ...config, 6 | proseWrap: 'always', 7 | }; 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>the-guild-org/shared-config:renovate"], 4 | "automerge": false, 5 | "lockFileMaintenance": { 6 | "enabled": true, 7 | "automerge": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "matchUpdateTypes": ["minor", "patch"], 12 | "groupName": "all non-major dependencies", 13 | "groupSlug": "all-minor-patch", 14 | "matchPackageNames": ["*"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.test.json", 3 | "exclude": ["**/dist", "**/node_modules/**", "**/__tests__", "**/tests"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "baseUrl": ".", 5 | "rootDir": "packages", 6 | "outDir": "dist", 7 | "target": "ES2021", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext"], 13 | "importHelpers": true, 14 | "preserveConstEnums": true, 15 | "downlevelIteration": true, 16 | "sourceMap": true, 17 | "declaration": true, 18 | "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", 19 | 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "skipLibCheck": true, 24 | "types": ["vitest/globals"], 25 | "paths": { 26 | "@graphql-inspector/core": ["packages/core/src/index.ts"], 27 | "@graphql-inspector/logger": ["packages/logger/src/index.ts"], 28 | "@graphql-inspector/ci": ["packages/ci/src/index.ts"], 29 | "@graphql-inspector/cli": ["packages/cli/src/index.ts"], 30 | "@graphql-inspector/config": ["packages/config/src/index.ts"], 31 | "@graphql-inspector/commands": ["packages/commands/commands/src/index.ts"], 32 | "@graphql-inspector/loaders": ["packages/loaders/loaders/src/index.ts"], 33 | "@graphql-inspector/action": ["packages/action/src/index.ts"], 34 | "@graphql-inspector/code-loader": ["packages/loaders/code/src/index.ts"], 35 | "@graphql-inspector/git-loader": ["packages/loaders/git/src/index.ts"], 36 | "@graphql-inspector/github-loader": ["packages/loaders/github/src/index.ts"], 37 | "@graphql-inspector/graphql-loader": ["packages/loaders/graphql/src/index.ts"], 38 | "@graphql-inspector/json-loader": ["packages/loaders/json/src/index.ts"], 39 | "@graphql-inspector/url-loader": ["packages/loaders/url/src/index.ts"], 40 | "@graphql-inspector/diff-command": ["packages/commands/diff/src/index.ts"], 41 | "@graphql-inspector/docs-command": ["packages/commands/docs/src/index.ts"], 42 | "@graphql-inspector/serve-command": ["packages/commands/serve/src/index.ts"], 43 | "@graphql-inspector/coverage-command": ["packages/commands/coverage/src/index.ts"], 44 | "@graphql-inspector/validate-command": ["packages/commands/validate/src/index.ts"], 45 | "@graphql-inspector/introspect-command": ["packages/commands/introspect/src/index.ts"], 46 | "@graphql-inspector/similar-command": ["packages/commands/similar/src/index.ts"], 47 | "@graphql-inspector/testing": ["packages/testing/src/index.ts"] 48 | } 49 | }, 50 | "include": ["packages"] 51 | } 52 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-inspector", 3 | "version": 2, 4 | "alias": ["graphql-inspector-app"], 5 | "env": { 6 | "LOG_FORMAT": "short", 7 | "LOG_LEVEL": "info", 8 | "DISABLE_STATS": "true", 9 | "LOG_LEVEL_IN_STRING": "true" 10 | }, 11 | "public": false, 12 | "redirects": [ 13 | { 14 | "source": "/install", 15 | "destination": "https://github.com/marketplace/graphql-inspector", 16 | "statusCode": 301 17 | }, 18 | { 19 | "source": "/action", 20 | "destination": "https://github.com/marketplace/actions/graphql-inspector", 21 | "statusCode": 301 22 | }, 23 | { 24 | "source": "/docs", 25 | "destination": "/docs/index", 26 | "statusCode": 301 27 | }, 28 | { 29 | "source": "/docs/recipes/github", 30 | "destination": "/docs/products/github", 31 | "statusCode": 301 32 | }, 33 | { 34 | "source": "/docs/recipes/action", 35 | "destination": "/docs/products/action", 36 | "statusCode": 301 37 | }, 38 | { 39 | "source": "/docs/recipes/ci", 40 | "destination": "/docs/products/ci", 41 | "statusCode": 301 42 | } 43 | ], 44 | "github": { 45 | "enabled": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: true, 8 | alias: { 9 | '@graphql-inspector/commands': 'packages/commands/commands/src/index.ts', 10 | '@graphql-inspector/loaders': 'packages/loaders/loaders/src/index.ts', 11 | '@graphql-inspector/logger': 'packages/logger/src/index.ts', 12 | '@graphql-inspector/url-loader': 'packages/loaders/url/src/index.ts', 13 | '@graphql-inspector/testing': 'packages/testing/src/index.ts', 14 | '@graphql-inspector/core': 'packages/core/src/index.ts', 15 | 'graphql/language/parser.js': 'graphql/language/parser.js', 16 | graphql: 'graphql/index.js', 17 | }, 18 | deps: { 19 | // fixes `graphql` Duplicate "graphql" modules cannot be used at the same time since different 20 | fallbackCJS: true, 21 | }, 22 | setupFiles: ['./packages/testing/src/setup-file.ts'], 23 | }, 24 | plugins: [ 25 | tsconfigPaths({ 26 | projects: [join(__dirname, 'tsconfig.test.json')], 27 | }), 28 | ], 29 | }); 30 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | out 4 | public/sitemap*.xml 5 | public/_redirects 6 | -------------------------------------------------------------------------------- /website/api/ping.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res) => { 2 | res.status(200).send('pong'); 3 | }; 4 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /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/inspector', 4 | generateIndexSitemap: false, 5 | exclude: ['*/_meta'], 6 | output: 'export', 7 | }; 8 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | import { withGuildDocs } from '@theguild/components/next.config'; 2 | 3 | export default withGuildDocs({ 4 | output: 'export', 5 | eslint: { 6 | ignoreDuringBuilds: true, 7 | }, 8 | redirects: () => 9 | Object.entries({ 10 | '/install': '/docs/installation', 11 | '/enterprise': '/docs', 12 | '/docs/index': '/docs', 13 | '/products': '/docs/products/ci', 14 | '/docs/recipies/github': '/docs/products/github', 15 | '/docs/api': '/docs/api/schema', 16 | '/docs/recipes': '/docs/recipes/environments', 17 | '/docs/recipes/github': '/docs/recipes/pull-requests', 18 | '/docs/essentials': '/docs/commands/diff', 19 | '/docs/essentials/diff': '/docs/commands/diff', 20 | '/docs/essentials/coverage': '/docs/commands/coverage', 21 | '/docs/essentials/validate': '/docs/commands/validate', 22 | '/docs/essentials/similar': '/docs/commands/similar', 23 | '/docs/essentials/audit': '/docs/commands/audit', 24 | '/docs/essentials/introspect': '/docs/commands/introspect', 25 | }).map(([from, to]) => ({ 26 | source: from, 27 | destination: to, 28 | permanent: true, 29 | })), 30 | }); 31 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "engines": { 7 | "node": ">=18.0.0" 8 | }, 9 | "scripts": { 10 | "build": "next build && next-sitemap", 11 | "dev": "next", 12 | "start": "next start" 13 | }, 14 | "dependencies": { 15 | "@graphql-inspector/core": "../packages/core/dist", 16 | "@monaco-editor/react": "4.6.0", 17 | "@theguild/components": "6.6.6", 18 | "next": "14.2.26", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-flip-move": "^3.0.5" 22 | }, 23 | "devDependencies": { 24 | "@theguild/tailwind-config": "0.4.2", 25 | "@types/node": "20.14.15", 26 | "@types/react": "18.3.3", 27 | "next-sitemap": "4.2.3", 28 | "typescript": "5.5.4" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('@theguild/tailwind-config/postcss.config'); 2 | -------------------------------------------------------------------------------- /website/public/assets/img/ci/diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/ci/diff.jpg -------------------------------------------------------------------------------- /website/public/assets/img/cli/coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/coverage.png -------------------------------------------------------------------------------- /website/public/assets/img/cli/coverageStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/coverageStats.png -------------------------------------------------------------------------------- /website/public/assets/img/cli/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/demo.gif -------------------------------------------------------------------------------- /website/public/assets/img/cli/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/demo.mp4 -------------------------------------------------------------------------------- /website/public/assets/img/cli/demo.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/demo.webm -------------------------------------------------------------------------------- /website/public/assets/img/cli/diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/diff.jpg -------------------------------------------------------------------------------- /website/public/assets/img/cli/github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/github.jpg -------------------------------------------------------------------------------- /website/public/assets/img/cli/similar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/similar.jpg -------------------------------------------------------------------------------- /website/public/assets/img/cli/validate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/cli/validate.jpg -------------------------------------------------------------------------------- /website/public/assets/img/diff-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/diff-result.png -------------------------------------------------------------------------------- /website/public/assets/img/enterprise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/enterprise.png -------------------------------------------------------------------------------- /website/public/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/favicon.png -------------------------------------------------------------------------------- /website/public/assets/img/favicon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/favicon/favicon.png -------------------------------------------------------------------------------- /website/public/assets/img/github/app-action.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/app-action.jpg -------------------------------------------------------------------------------- /website/public/assets/img/github/app-install.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/app-install.jpg -------------------------------------------------------------------------------- /website/public/assets/img/github/app-repositories.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/app-repositories.jpg -------------------------------------------------------------------------------- /website/public/assets/img/github/app-setup-plan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/app-setup-plan.jpg -------------------------------------------------------------------------------- /website/public/assets/img/github/intercept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/intercept.png -------------------------------------------------------------------------------- /website/public/assets/img/github/summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/github/summary.jpg -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/bug-fixing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/bug-fixing.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/business-deal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/business-deal.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/counting-stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/counting-stars.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/github.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/hacker-mindset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/hacker-mindset.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/hive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/hive.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/investment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/investment.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/mail-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/mail-box.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/new-ideas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/new-ideas.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/real-time-collaboration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/real-time-collaboration.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/result.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/server-cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/server-cluster.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/server.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/typewriter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/typewriter.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/winners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/winners.png -------------------------------------------------------------------------------- /website/public/assets/img/illustrations/yoga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/illustrations/yoga.png -------------------------------------------------------------------------------- /website/public/assets/img/just-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/img/logo-slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/logo-slack.png -------------------------------------------------------------------------------- /website/public/assets/img/notifications/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/notifications/discord.png -------------------------------------------------------------------------------- /website/public/assets/img/notifications/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/notifications/slack.png -------------------------------------------------------------------------------- /website/public/assets/img/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/og-image.png -------------------------------------------------------------------------------- /website/public/assets/img/serve-localhost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/serve-localhost.png -------------------------------------------------------------------------------- /website/public/assets/img/similar-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/similar-output.png -------------------------------------------------------------------------------- /website/public/assets/img/ui/arrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/annotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/ui/features/annotations.png -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/diff.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 43 | 46 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/intercept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/ui/features/intercept.png -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/ui/features/notifications.png -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/schema-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/assets/img/ui/features/schema-check.png -------------------------------------------------------------------------------- /website/public/assets/img/ui/features/validate.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/social/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/social/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/social/spectrum.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/img/ui/social/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/subheader-logo-w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/assets/subheader-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/graphql-inspector/83418298c1e21b3b57e66afdb6509071a50d6d26/website/public/favicon.png -------------------------------------------------------------------------------- /website/public/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Hello!" 3 | } 4 | -------------------------------------------------------------------------------- /website/src/components/diff/change.module.css: -------------------------------------------------------------------------------- 1 | .changeBox { 2 | margin-bottom: 5px; 3 | padding: 10px; 4 | display: block; 5 | border: 1px solid #4a4a4a; 6 | font-size: 12px; 7 | line-height: 30px; 8 | box-shadow: 0 25px 68px 0 rgba(0, 0, 0, 0.02); 9 | border-left-width: 3px; 10 | background-color: #354c87; 11 | color: #fff; 12 | border-radius: 5px; 13 | } 14 | 15 | .changeMessage { 16 | font-weight: 600; 17 | } 18 | 19 | .changeMessage span { 20 | padding: 5px 7px; 21 | background-color: #1e1e1e; 22 | border-radius: 5px; 23 | font-weight: 600; 24 | } 25 | -------------------------------------------------------------------------------- /website/src/components/diff/change.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import { Change, CriticalityLevel } from '@graphql-inspector/core'; 3 | import styles from './change.module.css'; 4 | 5 | const ColorMap = { 6 | [CriticalityLevel.Breaking]: '#d6231e', 7 | [CriticalityLevel.Dangerous]: '#f8b500', 8 | [CriticalityLevel.NonBreaking]: '#02a676', 9 | }; 10 | 11 | const SINGLE_QUOTES_REGEX = /'([^']+)'/g; 12 | const DOUBLE_QUOTES_REGEX = /"([^"]+)"/g; 13 | 14 | export default forwardRef(function ChangeComponent({ value }, ref) { 15 | const { message, criticality } = value; 16 | 17 | const formatted = message 18 | .replace(SINGLE_QUOTES_REGEX, (_, value) => `${value}`) 19 | .replace(DOUBLE_QUOTES_REGEX, (_, value) => `${value}`); 20 | 21 | return ( 22 |
29 |
30 |
31 | ); 32 | }); 33 | -------------------------------------------------------------------------------- /website/src/components/diff/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement, useCallback, useEffect, useState } from 'react'; 2 | import { buildSchema } from 'graphql'; 3 | import FlipMove from 'react-flip-move'; 4 | import { Change, diff } from '@graphql-inspector/core'; 5 | import { DiffEditor, DiffOnMount } from '@monaco-editor/react'; 6 | import ChangeComponent from './change'; 7 | 8 | const OLD_SCHEMA = /* GraphQL */ ` 9 | type Post { 10 | id: ID! 11 | title: String 12 | createdAt: String 13 | modifiedAt: String 14 | } 15 | 16 | type Query { 17 | post: Post! 18 | posts: [Post!] 19 | } 20 | `; 21 | 22 | const NEW_SCHEMA = /* GraphQL */ ` 23 | type Post { 24 | id: ID! 25 | title: String! 26 | createdAt: String 27 | } 28 | 29 | type Query { 30 | post: Post! 31 | } 32 | `; 33 | 34 | const oldSchema = buildSchema(OLD_SCHEMA); 35 | 36 | export const Diff = (): ReactElement => { 37 | const [code, setCode] = useState(NEW_SCHEMA); 38 | const [changes, setChanges] = useState([]); 39 | 40 | useEffect(() => { 41 | const run = async () => { 42 | try { 43 | const changes = await diff(oldSchema, buildSchema(code)); 44 | setChanges(changes); 45 | } catch (error) { 46 | console.error(error); 47 | } 48 | }; 49 | run(); 50 | }, [code]); 51 | 52 | const onDiffOnMount: DiffOnMount = useCallback(diffEditor => { 53 | const editor = diffEditor.getModifiedEditor(); 54 | editor.onKeyUp(() => { 55 | setCode(editor.getValue()); 56 | }); 57 | }, []); 58 | 59 | return ( 60 |
61 |
62 | 75 | 76 | {changes.map((change, i) => ( 77 | 78 | ))} 79 | 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default Diff; 86 | -------------------------------------------------------------------------------- /website/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | import { AppProps } from 'next/app'; 3 | import '@theguild/components/style.css'; 4 | 5 | export default function App({ Component, pageProps }: AppProps): ReactElement { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /website/src/pages/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: { 3 | title: 'Home', 4 | type: 'page', 5 | display: 'hidden', 6 | theme: { 7 | layout: 'raw', 8 | }, 9 | }, 10 | docs: { 11 | title: 'Documentation', 12 | type: 'page', 13 | theme: { 14 | toc: true, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /website/src/pages/docs/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: 'GraphQL Inspector', 3 | installation: 'Getting Started and Installation', 4 | commands: 'Commands', 5 | notifications: 'Schema Change Notifications', 6 | recipes: 'Recipes', 7 | products: 'Products', 8 | api: 'API', 9 | 'migration-guides': 'Migration Guides', 10 | }; 11 | -------------------------------------------------------------------------------- /website/src/pages/docs/api/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | schema: 'Providing Schema', 3 | documents: 'Providing Documents', 4 | }; 5 | -------------------------------------------------------------------------------- /website/src/pages/docs/api/documents.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | GraphQL Inspector - There are a few ways of providing operations and fragments (documents in 4 | general). 5 | --- 6 | 7 | import { Callout } from '@theguild/components' 8 | 9 | # Providing Documents 10 | 11 | There are a few ways of providing operations and fragments (documents in general). 12 | 13 | ## JavaScript And TypeScript Files 14 | 15 | GraphQL Inspector supports glob pattern. 16 | 17 | ``` 18 | ./src/app/**/*.ts 19 | ``` 20 | 21 | Given the example above, Inspector will search every file that matches that pattern and extract 22 | operations and fragments wrapped with `gql` or `graphql` template literal tag. 23 | 24 | Supported extensions: `.ts`, `.tsx`, `.js` and `.jsx`. 25 | 26 | ## GraphQL Files 27 | 28 | GraphQL Inspector supports glob pattern. 29 | 30 | ``` 31 | ./src/app/**/*.graphql 32 | ``` 33 | 34 | Given the example above, Inspector will search every file that matches that pattern and extract 35 | operations and fragments. 36 | 37 | Supported extensions: `.graphql`, `.graphqls` and `.gql`. 38 | 39 | 40 | Remember to wrap a glob pattern with quotes: `"./src/app/**/*.graphql"` 41 | 42 | 43 | ## Programmatic API 44 | 45 | If you are using programmatic API, you might find `@graphql-tools/load` package useful for loading 46 | documents. Learn more [here](https://graphql-tools.com/docs/documents-loading). 47 | 48 | ```js 49 | const { validate } = require('@graphql-inspector/core') 50 | const { loadDocuments } = require('@graphql-tools/load') 51 | const { GraphQLFileLoader } = require('@graphql-tools/graphql-file-loader') 52 | const graphql = require('graphql') 53 | 54 | const documents = loadDocuments('./src/**/*.graphql', { 55 | loaders: [new GraphQLFileLoader()] 56 | }) 57 | 58 | // Convert documents to the format expected by "validate" function 59 | const sources = documents.map(doc => { 60 | return new graphql.Source(graphql.print(doc.document), doc.location) 61 | }) 62 | 63 | const invalidDocuments = validate(schema, sources) 64 | ``` 65 | -------------------------------------------------------------------------------- /website/src/pages/docs/commands/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | diff: 'Diff', 3 | coverage: 'Coverage', 4 | audit: 'Audit', 5 | introspect: 'Introspect', 6 | serve: 'Serve', 7 | similar: 'Similar', 8 | validate: 'Validate', 9 | }; 10 | -------------------------------------------------------------------------------- /website/src/pages/docs/commands/audit.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | GraphQL Inspector Audit Documents - Audit your documents for useful metrics such as query depth, 4 | directive count and alias count. 5 | --- 6 | 7 | import { Callout } from '@theguild/components' 8 | 9 | # Audit (Documents) 10 | 11 | Audit your documents for useful metrics such as query depth, directive count and alias count. This 12 | is useful if you want to introduce security rules on your GraphQL server (e.g. via 13 | [`graphql-armor`](https://github.com/Escape-Technologies/graphql-armor)) and need to figure out the 14 | values for doing so. 15 | 16 | ## Audit - Usage 17 | 18 | Run the following command: 19 | 20 | ```sh 21 | graphql-inspector audit DOCUMENTS 22 | ``` 23 | 24 | ## Arguments 25 | 26 | - [`DOCUMENTS`](../api/documents) - a glob pattern that points to GraphQL Documents / Operations 27 | 28 | ## Flags 29 | 30 | - `-d, --detail` - Print statistics for each single GraphQL operation (default: `false`) 31 | - `--complexityScalarCost` - The cost of a scalar field for calculating the complexity score 32 | (default: `1`) 33 | - `--complexityObjectCost` - The cost of an object field for calculating the complexity score 34 | (default: `2`) 35 | - `--complexityDepthCostFactor` - The cost factor of a field per depth level for calculating the 36 | complexity score (default: `1.5`) 37 | 38 | ## Output 39 | 40 | A list of metrics including the maximum depth limit, maximum alias count and maximum directive 41 | count. 42 | 43 | This process does not fail, as it is mainly for showing useful information. In case you want to 44 | enforce a certain query deth, directive count or alias count limit check the 45 | [`--maxDepth`, `--maxAliasCount` and `--maxDirectiveCount` flags in the Validate Documents](./validate) 46 | documentation. 47 | 48 | ## Example 49 | 50 | ```sh 51 | graphql-inspector audit './documents/document.graphql' 52 | ``` 53 | 54 | Make sure to wrap the document in quotes. 55 | 56 | ### Example - Glob Pattern 57 | 58 | ```sh 59 | graphql-inspector audit './documents/*.graphql' 60 | ``` 61 | 62 | 63 | Make sure to wrap the glob pattern in quotes, otherwise the glob pattern will be expanded by your 64 | 65 | 66 | Glob patterns are not supported on GitHub Action and GitHub App. 67 | -------------------------------------------------------------------------------- /website/src/pages/docs/commands/introspect.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: GraphQL Inspector Introspection - Dumps an introspection file based on a schema. 3 | --- 4 | 5 | import { Callout } from '@theguild/components' 6 | 7 | # Introspection (Schema) 8 | 9 | Dumps an introspection file based on a schema. 10 | 11 | ## Introspection - Usage 12 | 13 | Run the following command: 14 | 15 | ```sh 16 | graphql-inspector introspect SCHEMA --write path/to/file 17 | ``` 18 | 19 | It supports `.graphql`, `.gql` and `.json` extensions. 20 | 21 | ## Arguments 22 | 23 | - [`SCHEMA`](../api/schema) - point to a schema 24 | 25 | ## Flags 26 | 27 | - `-w, --write ` - overwrite the output (default: `graphql.schema.json`) 28 | - `-r, --require ` - require a module 29 | - `-t, --token ` - an access token 30 | - `-h, --header ` - set HTTP header (`--header 'Auth: Basic 123'`) 31 | - `--method` - method on URL schema pointers (default: `POST`) 32 | - `--federation` - Support Apollo Federation directives (default: `false`) 33 | - `--aws` - Support AWS Appsync directives and scalar types (default: `false`) 34 | 35 | ## Output 36 | 37 | Writes a file with introspection result. 38 | 39 | 40 | We recommend to use `introspect` as part of a git hook. Having an always up-to-date schema file 41 | might improve your workflow. 42 | 43 | 44 | ## Examples 45 | 46 | ```sh 47 | graphql-inspector introspect example/schemas/new-valid.graphql --write example/schemas 48 | ``` 49 | 50 | ### Example file 51 | 52 | ```json 53 | { 54 | "__schema": { 55 | "description": null, 56 | "queryType": { 57 | "name": "Query" 58 | }, 59 | "mutationType": null, 60 | "subscriptionType": null, 61 | "types": [], 62 | "directives": [] 63 | } 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /website/src/pages/docs/commands/serve.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Serve a GraphQL schema with faked data - Takes a GraphQL schema and runs a server based on it, 4 | with faked data. 5 | --- 6 | 7 | # Serve With Faked Data (Schema) 8 | 9 | Takes a GraphQL schema and runs a server based on it, with faked data. 10 | 11 | ## Serve - Usage 12 | 13 | Run the following command: 14 | 15 | ```sh 16 | graphql-inspector serve SCHEMA 17 | ``` 18 | 19 | ## Arguments 20 | 21 | - [`SCHEMA`](../api/schema) - point to a schema 22 | 23 | ## Flags 24 | 25 | - `-r, --require ` - require a module 26 | - `-t, --token ` - an access token 27 | - `-h, --header ` - set HTTP header (`--header 'Auth: Basic 123'`) 28 | - `--method` - method on URL schema pointers (default: `POST`) 29 | - `--federation` - Support Apollo Federation directives (default: `false`) 30 | - `--aws` - Support AWS Appsync directives and scalar types (default: `false`) 31 | 32 | ## Output 33 | 34 | A server running on port `4000`. URL: `http://localhost:4000/graphql`. 35 | 36 | ## Examples 37 | 38 | ![serve](/assets/img/serve-localhost.png) 39 | -------------------------------------------------------------------------------- /website/src/pages/docs/commands/similar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: Find Similar Types - Get a list of similar types to find duplicates. 3 | --- 4 | 5 | # Similar (Schema) 6 | 7 | Get a list of similar types to find duplicates. 8 | 9 | ![Similar](/assets/img/cli/similar.jpg) 10 | 11 | ## Similar - Usage 12 | 13 | Run the following command: 14 | 15 | ```sh 16 | graphql-inspector similar SCHEMA 17 | ``` 18 | 19 | ## Arguments 20 | 21 | - [`SCHEMA`](../api/schema) - point to a schema 22 | 23 | ## Flags 24 | 25 | - `-n, --type ` - Check only a single type (_checks all types by default_) 26 | - `-t, --threshold ` - Threshold of similarity ratio (default: `0.4`) 27 | - `-w, --write ` - Write a file with results 28 | - `-r, --require ` - require a module 29 | - `-t, --token ` - an access token 30 | - `-h, --header ` - set HTTP header (`--header 'Auth: Basic 123'`) 31 | - `--method` - method on URL schema pointers (default: `POST`) 32 | - `--federation` - Support Apollo Federation directives (default: `false`) 33 | - `--aws` - Support AWS Appsync directives and scalar types (default: `false`) 34 | 35 | ## Output 36 | 37 | A list of similar types with the rating, grouped by a name. 38 | 39 | The process always succeeds: 40 | 41 | - When no similar types are found, the output is: 42 | 43 | ``` 44 | info No similar types found 45 | ``` 46 | 47 | - When similar types are found, the output is: ![similar-output](/assets/img/similar-output.png) 48 | 49 | ## Examples 50 | 51 | ```sh 52 | graphql-inspector similar example/schemas/deep.graphql 53 | ``` 54 | -------------------------------------------------------------------------------- /website/src/pages/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compare GraphQL Schemas, Validate Documents, Find Breaking Changes, Schema Coverage 3 | description: 4 | GraphQL Inspector is a set of tools to help you better maintain and improve GraphQL API as well as 5 | GraphQL consumers. 6 | --- 7 | 8 | # GraphQL Inspector 9 | 10 | GraphQL Inspector is a comprehensive suite of tools designed to assist you in maintaining and 11 | enhancing your GraphQL API, as well as the GraphQL clients consuming it. 12 | 13 | With GraphQL Inspector, you can compare two GraphQL schemas and generate a detailed list of changes. 14 | Each modification is labeled as breaking, non-breaking, or dangerous, with a precise explanation. 15 | Additionally, you can validate documents and fragments against your schema, detect similar or 16 | duplicated types, and write your own validation rules. 17 | 18 | GraphQL Inspector offers several ways to use its features, including a Command Line Tool, GitHub 19 | Application, GitHub Action, and Programmatic API. You can use the CLI and the programmatic API to 20 | build your own tools on top of GraphQL Inspector, or take advantage of the GitHub Application to get 21 | started quickly. 22 | 23 | Use GraphQL Inspector however you like: 24 | 25 | - **Command Line Tool** 26 | - **Programmatic API** 27 | - [**GitHub Action**](/docs/products/action) 28 | 29 | ![App and Action](/assets/img/github/app-action.jpg) 30 | 31 | ## Some of the key features of GraphQL Inspector include: 32 | 33 | - Comparing GraphQL schemas to detect changes. 34 | - Validating Operations and Fragments against the schema to identify deprecated usage. 35 | - Using a GitHub Application or GitHub Action to get up and running quickly. 36 | - Finding duplicated types within your schema. 37 | - Generating schema coverage reports based on Operations and Fragments. 38 | - Serving a GraphQL server with faked data and GraphiQL. 39 | - Introspecting a GraphQL API and saving the introspection result to a file. 40 | - GraphQL Inspector is a valuable tool for developers who want to maintain and enhance their GraphQL 41 | API. By identifying changes, validating documents, and detecting issues, it can help you ensure 42 | that your API is always up-to-date and working as expected. 43 | 44 | Ready to start? [Get started with GraphQL Inspector](/docs/installation). 45 | -------------------------------------------------------------------------------- /website/src/pages/docs/migration-guides/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'from-graphql-cli': 'From GraphQL-CLI', 3 | github: 'GitHub Application - Deprecated', 4 | 'from-app-to-action': 'From GitHub Application to GitHub Action', 5 | }; 6 | -------------------------------------------------------------------------------- /website/src/pages/docs/products/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | ci: 'Continuous Integration', 3 | action: 'GitHub Action', 4 | }; 5 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | environments: 'Using Environments', 3 | endpoints: 'Using GraphQL Endpoints', 4 | intercept: 'Intercepting Schema Changes', 5 | annotations: 'Annotations', 6 | 'pull-requests': 'Managing Pull Requests', 7 | }; 8 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/annotations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | GitHub offers in-code annotations and GraphQL Inspector, both App and Action enable you to use 4 | them. 5 | --- 6 | 7 | # Annotations 8 | 9 | GitHub offers in-code annotations and GraphQL Inspector, both App and Action enable you to use them. 10 | 11 | It's a nice and clean way to understand what has changed, and how it looked before and after. 12 | 13 | ![Annotations](/assets/img/cli/github.jpg) 14 | 15 | ## Disabling Annotations 16 | 17 | Annotations are enabled by default. 18 | 19 | ### Single Environment Setup 20 | 21 | ```yaml 22 | diff: 23 | annotations: false 24 | ``` 25 | 26 | ### Multiple Environment Setup 27 | 28 | ```yaml 29 | # Per environment 30 | env: 31 | production: 32 | branch: master 33 | diff: 34 | annotations: false 35 | 36 | # Or globally 37 | diff: 38 | annotations: false 39 | env: 40 | production: 41 | branch: master 42 | ``` 43 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/endpoints.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | In this chapter, we're going to use live and running GraphQL API instead of GraphQL file as the 4 | source of truth of your schema. 5 | --- 6 | 7 | import { Callout } from '@theguild/components' 8 | 9 | # Using GraphQL Endpoints 10 | 11 | In this chapter, we're going to use live and running GraphQL API instead of GraphQL file as the 12 | source of truth of your schema. This approach is very useful when you don't deploy on every push and 13 | your default branch is fine with temporary breaking changes. 14 | 15 | 16 | Using GitHub secrets is not possible at the moment, but we're working on it! 17 | 18 | 19 | ## Usage 20 | 21 | ### Single Environment Setup 22 | 23 | ```yaml 24 | # POST with no headers 25 | endpoint: '' 26 | schema: schema.graphql 27 | 28 | # GET or POST with headers 29 | schema: schema.graphql 30 | endpoint: 31 | url: '' 32 | method: GET 33 | headers: 34 | auth: Basic 35 | ``` 36 | 37 | ### Multiple Environment Setup 38 | 39 | ```yaml 40 | schema: schema.graphql 41 | env: 42 | production: 43 | branch: master 44 | endpoint: '' 45 | development: 46 | branch: develop 47 | endpoint: 48 | url: '' 49 | method: GET 50 | headers: 51 | auth: Basic 52 | ``` 53 | 54 | ## How it works 55 | 56 | Whenever possible, GraphQL Inspector introspects a given GraphQL endpoint and compares it with a 57 | GraphQL schema of the Pull Request. 58 | 59 | Two scenarios to consider here: 60 | 61 | 1. When a Pull Request to the `master` branch is created, GraphQL Inspector introspects a given 62 | GraphQL endpoint and compares it with a GraphQL schema of the Pull Request. 63 | 2. In case when your Pull Request targets a branch, let's call it `my-random-branch` and it's not a 64 | branch defined in any of the environments, GraphQL Inspector picks the schema file as the source 65 | of truth. 66 | 67 | 68 | When using GraphQL Inspector as GitHub Action, the endpoint is always the source of truth. 69 | 70 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/pull-requests.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | GraphQL Inspector runs a change detection on every Pull Request. It matches the target branch with 4 | the one defined in the Inspector configuration. 5 | --- 6 | 7 | # Managing Pull Requests 8 | 9 | GraphQL Inspector runs a change detection on every Pull Request. It matches the target branch with 10 | the one defined in the Inspector configuration. 11 | 12 | In cases of multiple environments, it iterates over all of them and compares a target branch of the 13 | Pull Request. In single environments is less complex. 14 | 15 | When GraphQL Inspector succeeds and finds the associated environment, it detects changes between 16 | source and target schemas. If a Pull Request is not related to any environment, the same thing 17 | happens. 18 | 19 | The described approach may get you into trouble because usually, you don't want to reject Pull 20 | Requests that are not (yet) affecting your environments. 21 | 22 | This is why Inspector lets you still run the change detection but without rejecting those Pull 23 | Requests. You just need to turn off the `failOnBreaking` flag. 24 | 25 | ## Single environment 26 | 27 | You keep all the options but disable the `failOnBreaking` flag. 28 | 29 | ```yaml 30 | diff: 31 | annotations: true 32 | schema: schema.graphql 33 | branch: master' 34 | 35 | others: 36 | diff: 37 | failOnBreaking: false 38 | ``` 39 | 40 | ## Multiple environments 41 | 42 | You keep all the global options but disable the `failOnBreaking` flag. 43 | 44 | ```yaml 45 | diff: 46 | annotations: true 47 | schema: schema.graphql 48 | 49 | env: 50 | production: 51 | branch: master 52 | preview: 53 | branch: develop 54 | 55 | others: 56 | diff: 57 | failOnBreaking: false 58 | ``` 59 | 60 | ## Disabling GitHub Action 61 | 62 | It's simple, use the [fail-on-breaking](../products/action#fail-on-breaking) input. 63 | -------------------------------------------------------------------------------- /website/src/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | --- 4 | 5 | export { IndexPage as default } from '../components/index-page' 6 | -------------------------------------------------------------------------------- /website/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export { default } from '@theguild/tailwind-config'; 2 | -------------------------------------------------------------------------------- /website/theme.config.tsx: -------------------------------------------------------------------------------- 1 | /* eslint sort-keys: error */ 2 | import { useRouter } from 'next/router'; 3 | import { defineConfig, Giscus, PRODUCTS, useTheme } from '@theguild/components'; 4 | 5 | export default defineConfig({ 6 | description: 'GraphQL schema management and evolution tools', 7 | docsRepositoryBase: 'https://github.com/graphql-hive/graphql-inspector/tree/master/website', // base URL for the docs repository 8 | logo: PRODUCTS.INSPECTOR.logo({ className: 'w-9' }), 9 | main: function Main({ children }) { 10 | const { resolvedTheme } = useTheme(); 11 | const { route } = useRouter(); 12 | 13 | const comments = route !== '/' && ( 14 | 24 | ); 25 | return ( 26 | <> 27 | {children} 28 | {comments} 29 | 30 | ); 31 | }, 32 | websiteName: 'GraphQL-Inspector', 33 | }); 34 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 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": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "useUnknownInCatchVariables": false 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | --------------------------------------------------------------------------------