{
8 | const program = yargs
9 | .scriptName('graphql')
10 | .detectLocale(false)
11 | .epilog('Visit https://github.com/Urigo/graphql-cli for more information')
12 | .version();
13 |
14 | const commandPackageNames = await discoverCommands();
15 | const commandFactories = await Promise.all(commandPackageNames.map(loadCommand));
16 |
17 | [discover, ...commandFactories].forEach((cmd) => {
18 | program.command(
19 | cmd({
20 | useConfig,
21 | useLoaders,
22 | })
23 | );
24 | });
25 |
26 | program.demandCommand().recommendCommands().help().showHelpOnFail(false).argv;
27 | }
28 |
29 | async function discoverCommands() {
30 | const commandNames: string[] = [];
31 | const paths = require.resolve.paths('graphql-cli');
32 |
33 | await Promise.all(paths.map(findInDirectory));
34 |
35 | async function findInDirectory(directory: string) {
36 | const results = await globby('*', {
37 | cwd: join(directory, '@graphql-cli'),
38 | onlyDirectories: true,
39 | deep: 1,
40 | ignore: ['common', 'loaders'],
41 | });
42 |
43 | if (results.length) {
44 | commandNames.push(...results);
45 | }
46 | }
47 |
48 | // unique names
49 | return commandNames.filter((val, i, list) => list.indexOf(val) === i);
50 | }
51 |
52 | function loadCommand(name: string): CommandFactory {
53 | const mod = require(`@graphql-cli/${name}`);
54 |
55 | return mod.default || mod;
56 | }
57 |
--------------------------------------------------------------------------------
/website/docs/coverage.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: coverage
3 | title: coverage
4 | sidebar_label: coverage
5 | ---
6 |
7 | Schema coverage based on documents. Find out how many times types and fields are used in your application. See the [official GraphQL Inspector documentation](https://graphql-inspector.com/docs/essentials/coverage) for details.
8 |
9 | ### Installation
10 |
11 | import Tabs from '@theme/Tabs';
12 | import TabItem from '@theme/TabItem';
13 |
14 |
21 |
22 |
23 | ```
24 | yarn global add @graphql-cli/coverage
25 | ```
26 |
27 |
28 |
29 |
30 |
31 | ```
32 | npm i -g @graphql-cli/coverage
33 | ```
34 |
35 |
36 |
37 |
38 | ### Usage
39 |
40 | ```
41 | graphql coverage [DOCUMENTS] [SCHEMA]
42 | ```
43 |
44 | #### Arguments
45 |
46 | | argument | description | default |
47 | | --- | --- | --- |
48 | | `DOCUMENTS` | A glob pattern that points to GraphQL Documents / Operations | `documents` property in GraphQL Config file |
49 | | `SCHEMA` | A pointer to a schema | `schema` property in GraphQL Config file |
50 |
51 | #### Options
52 |
53 | | option | alias | description | default |
54 | | --- | --- | --- | --- |
55 | | `--silent` | `-s` | Do not render any stats in the terminal | `false` |
56 | | `--write` | `-w` | Write a file with coverage stats | disabled |
57 | | `--deprecated` | `-d` | Fail on deprecated usage | `false` |
58 | | `--require` | `-r` | Require a module | `[]` |
59 | | `--token` | `-t` | An access token | `undefined` |
60 | | `--header` | `-h` | Set HTTP header (`--header 'Auth: Basic 123'`) | `undefined` |
61 |
--------------------------------------------------------------------------------
/templates/fullstack/client/src/components/comment/CreateComment.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useCreateCommentMutation } from '../../generated-types';
3 | import { Card, TextField, Button } from '@material-ui/core';
4 | import './../notes/Note.css';
5 |
6 | type createCommentProps = {
7 | noteId: string;
8 | addCommentState: any;
9 | };
10 |
11 | const CreateComment = ({ noteId, addCommentState }: createCommentProps) => {
12 | const [createComment] = useCreateCommentMutation();
13 | const [newCommentTitle, setNewCommentTitle] = useState('');
14 | const [newCommentDescription, setNewCommentDescription] = useState('');
15 |
16 | return (
17 |
18 |
19 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default CreateComment;
52 |
--------------------------------------------------------------------------------
/packages/commands/init/src/sources/from-existing.ts:
--------------------------------------------------------------------------------
1 | import rimraf from 'rimraf';
2 | import { join } from 'path';
3 | import { existsSync } from 'fs-extra';
4 | import { prompt } from 'inquirer';
5 | import { Context, PackageManifest } from '../common';
6 | import { searchCodegenConfig } from '../search-codegen-config';
7 |
8 | export async function fromExisting({ context, project }: { context: Context; project: PackageManifest }) {
9 | const manifestPath = join(context.path, 'package.json');
10 |
11 | if (existsSync(manifestPath)) {
12 | const { name: projectName } = require(manifestPath);
13 | context.name = projectName;
14 | }
15 |
16 | const result = await searchCodegenConfig(context.path);
17 |
18 | if (result && !result.isEmpty) {
19 | const codegenFilePath = result.filepath;
20 | const { willBeMerged } = await prompt([
21 | {
22 | type: 'confirm',
23 | name: 'willBeMerged',
24 | message: `GraphQL Code Generator configuration has been detected in ${codegenFilePath}.\n Do you want to use the same configuration with GraphQL CLI?`,
25 | default: true,
26 | },
27 | ]);
28 |
29 | if (willBeMerged) {
30 | project.addDependency('@graphql-cli/codegen');
31 | const codegenConfig = result.config;
32 |
33 | context.graphqlConfig.extensions.codegen = {
34 | generates: {},
35 | };
36 |
37 | for (const key in codegenConfig) {
38 | if (key === 'schema') {
39 | context.graphqlConfig.schema = codegenConfig.schema;
40 | } else if (key === 'documents') {
41 | context.graphqlConfig.documents = codegenConfig.documents;
42 | } else {
43 | context.graphqlConfig.extensions.codegen[key] = codegenConfig[key];
44 | }
45 | }
46 |
47 | rimraf.sync(result.filepath);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/commands/init/src/features/inspector.ts:
--------------------------------------------------------------------------------
1 | import { prompt } from 'inquirer';
2 | import { Context, PackageManifest, ProjectType } from '../common';
3 |
4 | export async function askForInspector({ context, project }: { context: Context; project: PackageManifest }) {
5 | if (context.type === ProjectType.FullStack || context.type === ProjectType.FrontendOnly) {
6 | const { isFrontendInspectorAsked } = await prompt([
7 | {
8 | type: 'confirm',
9 | name: 'isFrontendInspectorAsked',
10 | message: 'Do you want to have GraphQL Inspector tools for your frontend?',
11 | default: true,
12 | },
13 | ]);
14 |
15 | if (isFrontendInspectorAsked) {
16 | project.addDependency('@graphql-cli/coverage');
17 | project.addDependency('@graphql-cli/validate');
18 |
19 | project.addScript('graphql:coverage', 'graphql coverage');
20 | project.addScript('graphql:validate', 'graphql validate');
21 | }
22 | }
23 |
24 | if (context.type === ProjectType.FullStack || context.type === ProjectType.BackendOnly) {
25 | const { isBackendInspectorAsked } = await prompt([
26 | {
27 | type: 'confirm',
28 | name: 'isBackendInspectorAsked',
29 | message: 'Do you want to have GraphQL Inspector tools for your backend?',
30 | default: true,
31 | },
32 | ]);
33 |
34 | if (isBackendInspectorAsked) {
35 | project.addDependency('@graphql-cli/diff');
36 | project.addDependency('@graphql-cli/similar');
37 |
38 | project.addScript('graphql:diff', 'graphql diff');
39 | project.addScript('graphql:similar', 'graphql similar');
40 |
41 | if (!context.graphqlConfig.extensions.diff) {
42 | context.graphqlConfig.extensions.diff = {
43 | baseSchema: 'your-base-schema-here',
44 | };
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/templates/fullstack/client/src/components/notes/OneNote.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import EditNote from './EditNote';
3 | import { Note } from '../../generated-types';
4 | import CreateComment from '../comment/CreateComment';
5 | import OneComment from '../comment/OneComment';
6 | import { Button, Card } from '@material-ui/core';
7 |
8 | const OneNote = ({ id, title, description, comments }: Note) => {
9 | const [noteEdit, setNoteEdit] = useState(false);
10 | const [addComment, setAddComment] = useState(false);
11 |
12 | return (
13 |
14 |
15 |
16 | {title}:
17 | {description}
18 |
21 |
24 | {noteEdit ? (
25 |
26 | ) : (
27 |
28 | )}
29 | {addComment ? : }
30 |
31 | {comments && comments.length > 0 ? (
32 | comments.map((com) => {
33 | if (!com) {
34 | return;
35 | }
36 | return ;
37 | })
38 | ) : (
39 |
40 | )}
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default OneNote;
49 |
--------------------------------------------------------------------------------
/templates/fullstack/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.github/workflows/algolia-integrity.yml:
--------------------------------------------------------------------------------
1 | name: Algolia Integrity
2 | on:
3 | pull_request:
4 | paths:
5 | - 'website/**'
6 | jobs:
7 | algolia-records-check:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v3
12 | with:
13 | fetch-depth: 0
14 | - name: Use Node 18
15 | uses: actions/setup-node@v3
16 | with:
17 | node-version: 16
18 | cache: 'yarn'
19 | - name: Install Dependencies
20 | run: yarn --ignore-engines
21 | working-directory: ./website
22 |
23 | - name: Build Packages
24 | run: yarn build
25 | working-directory: ./website
26 |
27 | - name: Algolia generate
28 | run: yarn algolia-sync
29 | working-directory: ./website
30 | env:
31 | ALGOLIA_DRY_RUN: true
32 | SITE_URL: https://www.graphql-cli.com/
33 |
34 | - name: Yarn build at root
35 | run: yarn
36 |
37 | - name: Prettier
38 | run: yarn prettier -w website/algolia-lockfile.json
39 |
40 | - name: Compare
41 | run: git diff origin/${{ github.base_ref }}.. -- website/algolia-lockfile.json
42 |
43 | - name: Diff to file
44 | if: always()
45 | id: diff_result
46 | run: |
47 | OUTPUT=$(git diff origin/${{ github.base_ref }}.. -- website/algolia-lockfile.json)
48 | OUTPUT="${OUTPUT//'%'/'%25'}"
49 | OUTPUT="${OUTPUT//$'\n'/'%0A'}"
50 | OUTPUT="${OUTPUT//$'\r'/'%0D'}"
51 | echo "::set-output name=result::$OUTPUT"
52 |
53 | - name: Publish a message
54 | if: always() && contains(steps.diff_result.outputs.result, 'diff')
55 | uses: marocchino/sticky-pull-request-comment@v2
56 | with:
57 | message: |
58 | ```diff
59 | ${{ steps.diff_result.outputs.result }}
60 | ```
61 |
--------------------------------------------------------------------------------
/website/docs/validate.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: validate
3 | title: validate
4 | sidebar_label: validate
5 | ---
6 |
7 | Validates documents against a schema and looks for deprecated usage.. See the [official GraphQL Inspector documentation](https://graphql-inspector.com/docs/essentials/validate) for details.
8 |
9 | ### Installation
10 |
11 | import Tabs from '@theme/Tabs';
12 | import TabItem from '@theme/TabItem';
13 |
14 |
21 |
22 |
23 | ```
24 | yarn global add @graphql-cli/validate
25 | ```
26 |
27 |
28 |
29 |
30 |
31 | ```
32 | npm i -g @graphql-cli/validate
33 | ```
34 |
35 |
36 |
37 |
38 | ### Usage
39 |
40 | ```
41 | graphql validate [DOCUMENTS] [SCHEMA]
42 | ```
43 |
44 | #### Arguments
45 |
46 | | argument | description | default |
47 | | --- | --- | --- |
48 | | `DOCUMENTS` | A glob pattern that points to GraphQL Documents / Operations | `documents` property in GraphQL Config file |
49 | | `SCHEMA` | A pointer to a schema | `schema` property in GraphQL Config file |
50 |
51 | #### Options
52 |
53 | | option | alias | description | default |
54 | | --- | --- | --- | --- |
55 | | `--deprecated` | `-d` | Fail on deprecated usage | `false` |
56 | | `--noStrictFragments` | | Do not fail on duplicated fragment names | `false` |
57 | | `--apollo` | | Support Apollo directives (@client and @connection) | `false` |
58 | | `--keepClientFields` | | Keeps the fields with @client, but removes @client directive from them - works only with combination of `--apollo` | `false` |
59 | | `--maxDepth` | | Fail when operation depth exceeds maximum depth | `undefined` |
60 | | `--require` | `-r` | Require a module | `[]` |
61 | | `--token` | `-t` | An access token | `undefined` |
62 | | `--header` | `-h` | Set HTTP header (`--header 'Auth: Basic 123'`) | `undefined` |
63 |
--------------------------------------------------------------------------------
/.github/workflows/algolia-publish.yml:
--------------------------------------------------------------------------------
1 | name: Algolia Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | algolia-push-records:
10 | name: Push new records if changes
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout Repo
14 | uses: actions/checkout@v3
15 |
16 | - name: Use Node
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: 16
20 | cache: 'yarn'
21 |
22 | - name: Install Dependencies
23 | run: yarn
24 | working-directory: ./website
25 |
26 | - name: Build Packages
27 | run: yarn build
28 | working-directory: ./website
29 |
30 | - name: Algolia push
31 | run: yarn algolia-sync
32 | working-directory: ./website
33 | env:
34 | ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
35 | ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
36 | ALGOLIA_INDEX_NAME: ${{ secrets.ALGOLIA_INDEX_NAME }}
37 | SITE_URL: https://www.graphql-cli.com/
38 |
39 | - name: Yarn build at root
40 | run: yarn
41 |
42 | - name: Prettier
43 | run: yarn prettier -w website/algolia-lockfile.json
44 |
45 | - name: Compare
46 | run: git diff website/algolia-lockfile.json
47 |
48 | - name: Diff to file
49 | if: always()
50 | id: diff_result
51 | run: |
52 | OUTPUT=$(git diff website/algolia-lockfile.json)
53 | OUTPUT="${OUTPUT//'%'/'%25'}"
54 | OUTPUT="${OUTPUT//$'\n'/'%0A'}"
55 | OUTPUT="${OUTPUT//$'\r'/'%0D'}"
56 | echo "::set-output name=result::$OUTPUT"
57 |
58 | - name: Commit algolia-lockfile.json
59 | if: always() && contains(steps.diff_result.outputs.result, 'diff')
60 | uses: EndBug/add-and-commit@v9
61 | with:
62 | commit: website/algolia-lockfile.json
63 | message: Update algolia-lockfile.json
64 | default_author: github_actions
65 |
--------------------------------------------------------------------------------
/website/docs/codegen.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: codegen
3 | title: codegen
4 | sidebar_label: codegen
5 | ---
6 |
7 | Generate code from your GraphQL schema and operations. See the official [GraphQL Code Generator](https://graphql-code-generator.com/) site for complete documentation, guides and more.
8 |
9 | ### Installation
10 |
11 | import Tabs from '@theme/Tabs';
12 | import TabItem from '@theme/TabItem';
13 |
14 |
21 |
22 |
23 | ```
24 | yarn global add @graphql-cli/codegen
25 | ```
26 |
27 |
28 |
29 |
30 |
31 | ```
32 | npm i -g @graphql-cli/codegen
33 | ```
34 |
35 |
36 |
37 |
38 | Note: GraphQL Code Generator also utilizes a plugin system, so make sure you also install any plugins you include inside your configuration. See [here](https://graphql-code-generator.com/docs/plugins/index) for a list of plugins.
39 |
40 | ### Example Configuration
41 |
42 | ```yml
43 | schema:
44 | - http://localhost:4000/graphql
45 | extensions:
46 | codegen:
47 | generates:
48 | ./graphql.schema.json:
49 | plugins:
50 | - "introspection"
51 | ```
52 |
53 | See [the docs](https://graphql-code-generator.com/docs/getting-started/codegen-config) for more details.
54 |
55 | ### Usage
56 |
57 | ```
58 | graphql codegen
59 | ```
60 |
61 | #### Arguments
62 |
63 | *None*
64 |
65 | #### Options
66 |
67 | | option | alias | description | default |
68 | | --- | --- | --- | --- |
69 | | `--config` | `-c` | Path to GraphQL codegen YAML config file | `codegen.yml` or GraphQL configuration file in cwd |
70 | | `--watch` | `-w` | Watch for changes and execute generation automatically. You can also specify a glob expreession for custom watch list. | |
71 | | `--require` | `-r` | Loads specific require.extensions before running the codegen and reading the configuration | `[]` |
72 | | `--overwrite` | `-o` | Overwrites existing files | `true` |
73 | | `--silent` | `-s` | Suppresses printing errors | `false` |
74 | | `--project` | `-p` | Name of a project in GraphQL Config | `undefined` |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | release:
11 | types: [released, prereleased]
12 |
13 | jobs:
14 | test:
15 | name: Testing on ${{matrix.os}} and Node ${{matrix.node_version}}
16 | runs-on: ${{matrix.os}}
17 | strategy:
18 | matrix:
19 | os: [ubuntu-latest, windows-latest]
20 | node_version: [12, 14, 16]
21 | steps:
22 | - name: Checkout Master
23 | uses: actions/checkout@v2
24 | - name: Use Node ${{matrix.node_version}}
25 | uses: actions/setup-node@master
26 | with:
27 | version: ${{ matrix.node_version }}
28 | - name: Install Dependencies using Yarn
29 | run: yarn --ignore-engines
30 | - name: Build
31 | run: yarn build
32 | - name: Test
33 | run: yarn test
34 |
35 | publish-canary:
36 | name: Publish Canary
37 | runs-on: ubuntu-latest
38 | if: ${{ github.event_name != 'release' }}
39 | steps:
40 | - name: Checkout Master
41 | uses: actions/checkout@v2
42 | - name: Use Node
43 | uses: actions/setup-node@v2
44 | with:
45 | node-version: 14
46 | - name: Install Dependencies using Yarn
47 | run: yarn --ignore-engines
48 | - name: Build
49 | run: yarn build
50 | - name: Release Canary
51 | run: |
52 | echo "Fork PR: ${{github.repository}}"
53 | if [ "${{github.repository}}" == "Urigo/graphql-cli" ] && [ "${{ secrets.NODE_AUTH_TOKEN }}" != "" ]
54 | then
55 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NODE_AUTH_TOKEN }}" > .npmrc
56 | npm run release:canary
57 | else
58 | echo "Skipping canary publish due to a fork/PR..."
59 | fi
60 |
61 | publish:
62 | # publish to npm only when doing the release
63 | if: ${{ github.event_name == 'release' }}
64 | name: Publish Release
65 | runs-on: ubuntu-latest
66 | steps:
67 | - name: Checkout Master
68 | uses: actions/checkout@v2
69 | - name: Use Node
70 | uses: actions/setup-node@v2
71 | with:
72 | node-version: 14
73 | - name: Install Dependencies using Yarn
74 | run: yarn --ignore-engines
75 | - name: Build
76 | run: yarn build
77 | - name: Release
78 | run: echo "//registry.npmjs.org/:_authToken=${{secrets.NODE_AUTH_TOKEN}}" > ~/.npmrc && TAG=${GITHUB_REF#"refs/tags/"} npm run release
79 |
--------------------------------------------------------------------------------
/templates/fullstack/client/src/graphql/graphback.graphql:
--------------------------------------------------------------------------------
1 | fragment NoteFields on Note {
2 | id
3 | title
4 | description
5 |
6 | }
7 |
8 | fragment NoteExpandedFields on Note {
9 | id
10 | title
11 | description
12 | comments {
13 | id
14 | text
15 | description
16 | }
17 | }
18 |
19 | fragment CommentFields on Comment {
20 | id
21 | text
22 | description
23 |
24 | }
25 |
26 | fragment CommentExpandedFields on Comment {
27 | id
28 | text
29 | description
30 | note {
31 | id
32 | title
33 | description
34 | }
35 | }
36 |
37 | query findNotes($filter: NoteFilter, $page: PageRequest, $orderBy: OrderByInput) {
38 | findNotes(filter: $filter, page: $page, orderBy: $orderBy) {
39 | items {
40 | ...NoteExpandedFields
41 | }
42 | offset
43 | limit
44 | count
45 | }
46 | }
47 |
48 | query getNote($id: ID!) {
49 | getNote(id: $id) {
50 | ...NoteExpandedFields
51 | }
52 | }
53 |
54 | query findComments($filter: CommentFilter, $page: PageRequest, $orderBy: OrderByInput) {
55 | findComments(filter: $filter, page: $page, orderBy: $orderBy) {
56 | items {
57 | ...CommentExpandedFields
58 | }
59 | offset
60 | limit
61 | count
62 | }
63 | }
64 |
65 | query getComment($id: ID!) {
66 | getComment(id: $id) {
67 | ...CommentExpandedFields
68 | }
69 | }
70 |
71 | mutation createNote($input: CreateNoteInput!) {
72 | createNote(input: $input) {
73 | ...NoteFields
74 | }
75 | }
76 |
77 |
78 | mutation updateNote($input: MutateNoteInput!) {
79 | updateNote(input: $input) {
80 | ...NoteFields
81 | }
82 | }
83 |
84 |
85 | mutation deleteNote($input: MutateNoteInput!) {
86 | deleteNote(input: $input) {
87 | ...NoteFields
88 | }
89 | }
90 |
91 |
92 | mutation createComment($input: CreateCommentInput!) {
93 | createComment(input: $input) {
94 | ...CommentFields
95 | }
96 | }
97 |
98 |
99 | mutation updateComment($input: MutateCommentInput!) {
100 | updateComment(input: $input) {
101 | ...CommentFields
102 | }
103 | }
104 |
105 |
106 | mutation deleteComment($input: MutateCommentInput!) {
107 | deleteComment(input: $input) {
108 | ...CommentFields
109 | }
110 | }
111 |
112 |
113 | subscription newNote($filter: NoteSubscriptionFilter) {
114 | newNote(filter: $filter) {
115 | ...NoteFields
116 | }
117 | }
118 |
119 | subscription updatedNote($filter: NoteSubscriptionFilter) {
120 | updatedNote(filter: $filter) {
121 | ...NoteFields
122 | }
123 | }
124 |
125 | subscription deletedNote($filter: NoteSubscriptionFilter) {
126 | deletedNote(filter: $filter) {
127 | ...NoteFields
128 | }
129 | }
130 |
131 | subscription newComment($filter: CommentSubscriptionFilter) {
132 | newComment(filter: $filter) {
133 | ...CommentFields
134 | }
135 | }
136 |
137 | subscription updatedComment($filter: CommentSubscriptionFilter) {
138 | updatedComment(filter: $filter) {
139 | ...CommentFields
140 | }
141 | }
142 |
143 | subscription deletedComment($filter: CommentSubscriptionFilter) {
144 | deletedComment(filter: $filter) {
145 | ...CommentFields
146 | }
147 | }
--------------------------------------------------------------------------------
/integration/test-project/schema/schema.graphql:
--------------------------------------------------------------------------------
1 | ## NOTE: This schema was generated by Graphback and should not be changed manually
2 |
3 | """Exposes a URL that specifies the behaviour of this scalar."""
4 | directive @specifiedBy(
5 | """The URL that specifies the behaviour of this scalar."""
6 | url: String!
7 | ) on SCALAR
8 |
9 | """ @model """
10 | type Comment {
11 | id: ID!
12 | text: String
13 | description: String
14 |
15 | """@manyToOne(field: 'comments', key: 'noteId')"""
16 | note: Note
17 | }
18 |
19 | input CommentFilter {
20 | id: IDInput
21 | text: StringInput
22 | description: StringInput
23 | noteId: IDInput
24 | and: [CommentFilter!]
25 | or: [CommentFilter!]
26 | not: CommentFilter
27 | }
28 |
29 | type CommentResultList {
30 | items: [Comment]!
31 | offset: Int
32 | limit: Int
33 | count: Int
34 | }
35 |
36 | input CommentSubscriptionFilter {
37 | id: ID
38 | text: String
39 | description: String
40 | }
41 |
42 | input CreateCommentInput {
43 | id: ID
44 | text: String
45 | description: String
46 | noteId: ID
47 | }
48 |
49 | input CreateNoteInput {
50 | id: ID
51 | title: String!
52 | description: String
53 | }
54 |
55 | input IDInput {
56 | ne: ID
57 | eq: ID
58 | le: ID
59 | lt: ID
60 | ge: ID
61 | gt: ID
62 | in: [ID!]
63 | }
64 |
65 | input MutateCommentInput {
66 | id: ID!
67 | text: String
68 | description: String
69 | noteId: ID
70 | }
71 |
72 | input MutateNoteInput {
73 | id: ID!
74 | title: String
75 | description: String
76 | }
77 |
78 | type Mutation {
79 | createNote(input: CreateNoteInput!): Note
80 | updateNote(input: MutateNoteInput!): Note
81 | deleteNote(input: MutateNoteInput!): Note
82 | createComment(input: CreateCommentInput!): Comment
83 | updateComment(input: MutateCommentInput!): Comment
84 | deleteComment(input: MutateCommentInput!): Comment
85 | }
86 |
87 | """ @model """
88 | type Note {
89 | id: ID!
90 | title: String!
91 | description: String
92 |
93 | """@oneToMany(field: 'note', key: 'noteId')"""
94 | comments(filter: CommentFilter): [Comment]!
95 | }
96 |
97 | input NoteFilter {
98 | id: IDInput
99 | title: StringInput
100 | description: StringInput
101 | and: [NoteFilter!]
102 | or: [NoteFilter!]
103 | not: NoteFilter
104 | }
105 |
106 | type NoteResultList {
107 | items: [Note]!
108 | offset: Int
109 | limit: Int
110 | count: Int
111 | }
112 |
113 | input NoteSubscriptionFilter {
114 | id: ID
115 | title: String
116 | description: String
117 | }
118 |
119 | input OrderByInput {
120 | field: String!
121 | order: SortDirectionEnum = ASC
122 | }
123 |
124 | input PageRequest {
125 | limit: Int
126 | offset: Int
127 | }
128 |
129 | type Query {
130 | getNote(id: ID!): Note
131 | findNotes(filter: NoteFilter, page: PageRequest, orderBy: OrderByInput): NoteResultList!
132 | getComment(id: ID!): Comment
133 | findComments(filter: CommentFilter, page: PageRequest, orderBy: OrderByInput): CommentResultList!
134 | }
135 |
136 | enum SortDirectionEnum {
137 | DESC
138 | ASC
139 | }
140 |
141 | input StringInput {
142 | ne: String
143 | eq: String
144 | le: String
145 | lt: String
146 | ge: String
147 | gt: String
148 | in: [String!]
149 | contains: String
150 | startsWith: String
151 | endsWith: String
152 | }
153 |
154 | type Subscription {
155 | newNote(filter: NoteSubscriptionFilter): Note!
156 | updatedNote(filter: NoteSubscriptionFilter): Note!
157 | deletedNote(filter: NoteSubscriptionFilter): Note!
158 | newComment(filter: CommentSubscriptionFilter): Comment!
159 | updatedComment(filter: CommentSubscriptionFilter): Comment!
160 | deletedComment(filter: CommentSubscriptionFilter): Comment!
161 | }
--------------------------------------------------------------------------------
/templates/fullstack/server/src/schema/schema.graphql:
--------------------------------------------------------------------------------
1 | ## NOTE: This schema was generated by Graphback and should not be changed manually
2 |
3 | """Exposes a URL that specifies the behaviour of this scalar."""
4 | directive @specifiedBy(
5 | """The URL that specifies the behaviour of this scalar."""
6 | url: String!
7 | ) on SCALAR
8 |
9 | """ @model """
10 | type Comment {
11 | id: ID!
12 | text: String
13 | description: String
14 |
15 | """@manyToOne(field: 'comments', key: 'noteId')"""
16 | note: Note
17 | }
18 |
19 | input CommentFilter {
20 | id: IDInput
21 | text: StringInput
22 | description: StringInput
23 | noteId: IDInput
24 | and: [CommentFilter!]
25 | or: [CommentFilter!]
26 | not: CommentFilter
27 | }
28 |
29 | type CommentResultList {
30 | items: [Comment]!
31 | offset: Int
32 | limit: Int
33 | count: Int
34 | }
35 |
36 | input CommentSubscriptionFilter {
37 | id: ID
38 | text: String
39 | description: String
40 | }
41 |
42 | input CreateCommentInput {
43 | id: ID
44 | text: String
45 | description: String
46 | noteId: ID
47 | }
48 |
49 | input CreateNoteInput {
50 | id: ID
51 | title: String!
52 | description: String
53 | }
54 |
55 | input IDInput {
56 | ne: ID
57 | eq: ID
58 | le: ID
59 | lt: ID
60 | ge: ID
61 | gt: ID
62 | in: [ID!]
63 | }
64 |
65 | input MutateCommentInput {
66 | id: ID!
67 | text: String
68 | description: String
69 | noteId: ID
70 | }
71 |
72 | input MutateNoteInput {
73 | id: ID!
74 | title: String
75 | description: String
76 | }
77 |
78 | type Mutation {
79 | createNote(input: CreateNoteInput!): Note
80 | updateNote(input: MutateNoteInput!): Note
81 | deleteNote(input: MutateNoteInput!): Note
82 | createComment(input: CreateCommentInput!): Comment
83 | updateComment(input: MutateCommentInput!): Comment
84 | deleteComment(input: MutateCommentInput!): Comment
85 | }
86 |
87 | """ @model """
88 | type Note {
89 | id: ID!
90 | title: String!
91 | description: String
92 |
93 | """@oneToMany(field: 'note', key: 'noteId')"""
94 | comments(filter: CommentFilter): [Comment]!
95 | }
96 |
97 | input NoteFilter {
98 | id: IDInput
99 | title: StringInput
100 | description: StringInput
101 | and: [NoteFilter!]
102 | or: [NoteFilter!]
103 | not: NoteFilter
104 | }
105 |
106 | type NoteResultList {
107 | items: [Note]!
108 | offset: Int
109 | limit: Int
110 | count: Int
111 | }
112 |
113 | input NoteSubscriptionFilter {
114 | id: ID
115 | title: String
116 | description: String
117 | }
118 |
119 | input OrderByInput {
120 | field: String!
121 | order: SortDirectionEnum = ASC
122 | }
123 |
124 | input PageRequest {
125 | limit: Int
126 | offset: Int
127 | }
128 |
129 | type Query {
130 | getNote(id: ID!): Note
131 | findNotes(filter: NoteFilter, page: PageRequest, orderBy: OrderByInput): NoteResultList!
132 | getComment(id: ID!): Comment
133 | findComments(filter: CommentFilter, page: PageRequest, orderBy: OrderByInput): CommentResultList!
134 | }
135 |
136 | enum SortDirectionEnum {
137 | DESC
138 | ASC
139 | }
140 |
141 | input StringInput {
142 | ne: String
143 | eq: String
144 | le: String
145 | lt: String
146 | ge: String
147 | gt: String
148 | in: [String!]
149 | contains: String
150 | startsWith: String
151 | endsWith: String
152 | }
153 |
154 | type Subscription {
155 | newNote(filter: NoteSubscriptionFilter): Note!
156 | updatedNote(filter: NoteSubscriptionFilter): Note!
157 | deletedNote(filter: NoteSubscriptionFilter): Note!
158 | newComment(filter: CommentSubscriptionFilter): Comment!
159 | updatedComment(filter: CommentSubscriptionFilter): Comment!
160 | deletedComment(filter: CommentSubscriptionFilter): Comment!
161 | }
--------------------------------------------------------------------------------
/website/docusaurus.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'GraphQL CLI',
3 | tagline: '📟 Command line tool for common GraphQL development workflows',
4 |
5 | url: 'https://graphql-cli.com',
6 | baseUrl: '/',
7 | favicon: 'img/logo.ico',
8 |
9 | organizationName: 'urigo',
10 | projectName: 'graphql-cli',
11 |
12 | themeConfig: {
13 | colorMode: {
14 | disableSwitch: true,
15 | },
16 | image: 'img/logo.png',
17 | navbar: {
18 | title: 'GraphQL CLI',
19 | logo: {
20 | alt: 'GraphQL CLI Logo',
21 | src: 'img/logo.png',
22 | },
23 | items: [
24 | {
25 | to: '/introduction',
26 | label: 'Documentation',
27 | position: 'right',
28 | },
29 | {
30 | href: 'https://github.com/urigo/graphql-cli',
31 | label: 'GitHub',
32 | position: 'right',
33 | },
34 | ],
35 | },
36 | footer: {
37 | style: 'dark',
38 | copyright: `Copyright © ${new Date().getFullYear()} The Guild. All rights reserved.`,
39 | links: [
40 | {
41 | title: 'Docs',
42 | items: [
43 | {
44 | label: 'Introduction',
45 | to: 'introduction',
46 | },
47 | ],
48 | },
49 | {
50 | title: 'Community',
51 | items: [
52 | {
53 | label: 'Discord',
54 | href: 'https://discord.gg/xud7bH9',
55 | },
56 | {
57 | label: 'Other projects',
58 | href: 'https://github.com/the-guild-org/Stack',
59 | },
60 | {
61 | label: 'Mailing List',
62 | href: 'https://upscri.be/19qjhi',
63 | },
64 | {
65 | label: 'Community Meetings',
66 | href: 'https://github.com/the-guild-org/community-meetings',
67 | },
68 | ],
69 | },
70 | {
71 | title: 'Social',
72 | items: [
73 | {
74 | label: 'Blog',
75 | href: 'https://the-guild.dev/blog',
76 | },
77 | {
78 | label: 'GitHub',
79 | href: 'https://github.com/urigo/graphql-cli',
80 | },
81 | {
82 | label: 'Twitter',
83 | href: 'https://twitter.com/TheGuildDev',
84 | },
85 | {
86 | label: 'LinkedIn',
87 | href: 'https://www.linkedin.com/company/the-guild-software',
88 | },
89 | ],
90 | },
91 | ],
92 | },
93 | prism: {
94 | theme: require('prism-react-renderer/themes/dracula'),
95 | },
96 | },
97 | scripts: ['/js/light-mode-by-default.js'],
98 | customFields: {
99 | algolia: {
100 | appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
101 | searchApiKey: process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY,
102 | indexName: process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME,
103 | },
104 | },
105 | presets: [
106 | [
107 | require.resolve('@docusaurus/preset-classic'),
108 | {
109 | docs: {
110 | path: 'docs',
111 | routeBasePath: '/',
112 | include: ['**/*.md', '**/*.mdx'],
113 | sidebarPath: require.resolve('./sidebars.js'),
114 | editUrl: 'https://github.com/urigo/graphql-cli/edit/master/website/',
115 | },
116 | theme: {
117 | customCss: require.resolve('./src/css/custom.css'),
118 | },
119 | sitemap: {
120 | // cacheTime: 600 * 1001, // 600 sec - cache purge period
121 | changefreq: 'weekly',
122 | priority: 0.5,
123 | },
124 | },
125 | ],
126 | ],
127 | };
128 |
--------------------------------------------------------------------------------
/website/docs/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: introduction
3 | title: Introduction
4 | sidebar_label: Introduction
5 | ---
6 |
7 | 
8 |
9 | ### Features
10 |
11 | * Helpful commands to improve your workflows
12 | * Compatible with editors and IDEs based on [graphql-config](https://www.graphql-config.com)
13 | * Powerful plugin system to extend graphql-cli with custom commands
14 |
15 | ### Installation
16 |
17 | Run the following command to install GraphQL CLI globally:
18 |
19 | import Tabs from '@theme/Tabs';
20 | import TabItem from '@theme/TabItem';
21 |
22 |
29 |
30 |
31 | ```
32 | yarn global add graphql-cli graphql
33 | ```
34 |
35 |
36 |
37 |
38 |
39 | ```
40 | npm i -g graphql-cli graphql
41 | ```
42 |
43 |
44 |
45 |
46 | ### Configuration
47 |
48 | GraphQL CLI utilizes GraphQL Config for its configuration. You can learn more about GraphQL Config [here](https://www.graphql-config.com). The easiest way to get started is to run `init` command from your desired workspace:
49 |
50 | ```
51 | npx graphql-cli init
52 | ```
53 |
54 | After a series of questions from the command-prompt, the command will use the inputs and selected project templates to generate your configuration file for you. You can also write your own configuration file using an editor of your choice. For example, you could create a `.graphqlrc.yml` file with the following content:
55 |
56 | ```
57 | schema: "server/src/schema/**/*.graphql"
58 | documents: "client/src/documents/**/*.graphql"
59 | ```
60 |
61 | If you can run the `init` command with an existing file like the one above, its contents will be included with inputs you provide.
62 |
63 | ### Commands
64 |
65 | Each command in GraphQL CLI is treated as a plugin. In order to use the command, you have to install it first. Each command's package name follows this pattern: `@graphql-cli/[COMMAND-NAME]`. So to install the `init` command we used above, we would run
66 |
67 |
74 |
75 |
76 | ```
77 | yarn global add @graphql-cli/init
78 | ```
79 |
80 |
81 |
82 |
83 |
84 | ```
85 | npm i -g @graphql-cli/init
86 | ```
87 |
88 |
89 |
90 |
91 | After installing the command, we can then run it like this:
92 |
93 | ```
94 | graphql init
95 | ```
96 |
97 | Each command can be configured by updating the `extensions` field in your configuration file (`.graphqlrc.yml`). For example, if we install the `codegen` command, we can provide additional options to it like this:
98 |
99 | ```yml
100 | schema:
101 | ./server/src/schema/**/*.ts:
102 | require: ts-node/register
103 | documents: ./client/src/graphql/**/*.ts
104 | extensions:
105 | codegen:
106 | generates:
107 | ./server/src/generated-types.d.ts:
108 | plugins:
109 | - typescript
110 | - typescript-resolvers
111 | ./client/src/generated-types.tsx:
112 | plugins:
113 | - typescript
114 | - typescript-operations
115 | - typescript-react-apollo
116 | ```
117 |
118 | You can learn more about each command by navigating to its page from the menu. You can also write your own commands; see [this guide](custom-commands) for a detailed explanation.
119 |
120 | :::tip
121 | Note: You can execute the command `graphql discover` to open a list of GraphQL CLI plugins you can still. This is the only command that is available without installing additional packages.
122 | :::
123 |
--------------------------------------------------------------------------------
/scripts/release.js:
--------------------------------------------------------------------------------
1 | const { argv } = require('yargs');
2 | const { sync: glob } = require('globby');
3 | const { writeFile } = require('fs-extra');
4 | const { resolve, dirname, join } = require('path');
5 | const semver = require('semver');
6 | const cp = require('child_process');
7 | const rootPackageJson = require('../package.json');
8 |
9 | async function release() {
10 | let version = process.env.RELEASE_VERSION || rootPackageJson.version;
11 |
12 | if (version.startsWith('v')) {
13 | version = version.replace('v', '');
14 | }
15 |
16 | let tag = argv.tag || 'latest';
17 |
18 | if (argv.canary) {
19 | const gitHash = cp.spawnSync('git', ['rev-parse', '--short', 'HEAD']).stdout.toString().trim();
20 | version = semver.inc(version, 'prerelease', true, 'alpha-' + gitHash);
21 | tag = 'canary';
22 | }
23 |
24 | const workspaceGlobs = (rootPackageJson.workspaces.packages || rootPackageJson.workspaces).map(
25 | (workspace) => workspace + '/package.json'
26 | );
27 | const packageJsonPaths = glob(workspaceGlobs).map((packageJsonPath) => resolve(process.cwd(), packageJsonPath));
28 | const packageNames = packageJsonPaths.map((packageJsonPath) => require(packageJsonPath).name);
29 |
30 | rootPackageJson.version = version;
31 |
32 | await writeFile(resolve(__dirname, '../package.json'), JSON.stringify(rootPackageJson, null, 2));
33 |
34 | await Promise.all(
35 | packageJsonPaths.map(async (packageJsonPath) => {
36 | const packageJson = require(packageJsonPath);
37 |
38 | packageJson.version = version;
39 |
40 | for (const dependency in packageJson.dependencies) {
41 | if (packageNames.includes(dependency)) {
42 | packageJson.dependencies[dependency] = version;
43 | }
44 | }
45 |
46 | for (const dependency in packageJson.devDependencies) {
47 | if (packageNames.includes(dependency)) {
48 | packageJson.devDependencies[dependency] = version;
49 | }
50 | }
51 |
52 | await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
53 |
54 | if (!packageJson.private) {
55 | const distDirName = (packageJson.publishConfig && packageJson.publishConfig.directory) || '';
56 | const distPath = join(dirname(packageJsonPath), distDirName);
57 |
58 | //Fix package.json in dist directory
59 | const distPackageJsonPath = join(distPath, 'package.json');
60 | const distPackageJson = require(distPackageJsonPath);
61 |
62 | distPackageJson.name = packageJson.name;
63 | distPackageJson.version = packageJson.version;
64 | distPackageJson.dependencies = packageJson.dependencies;
65 | distPackageJson.devDependencies = packageJson.devDependencies;
66 | distPackageJson.publishConfig = {
67 | access: (packageJson.publishConfig && packageJson.publishConfig.access) || 'public',
68 | };
69 |
70 | await writeFile(distPackageJsonPath, JSON.stringify(distPackageJson, null, 2));
71 |
72 | return new Promise((resolve, reject) => {
73 | const publishSpawn = cp.spawn('npm', [
74 | 'publish',
75 | distPath,
76 | '--tag',
77 | tag,
78 | '--access',
79 | distPackageJson.publishConfig.access,
80 | ]);
81 |
82 | publishSpawn.stdout.on('data', (data) => {
83 | console.info(data.toString('utf8'));
84 | });
85 |
86 | publishSpawn.stderr.on('data', function (data) {
87 | console.error(data.toString('utf8'));
88 | });
89 |
90 | publishSpawn.on('exit', function (code, signal) {
91 | if (code !== 0) {
92 | reject(new Error(`npm publish exited with code: ${code} and signal: ${signal}`));
93 | } else {
94 | resolve();
95 | }
96 | });
97 | });
98 | }
99 | })
100 | );
101 |
102 | console.info(`${tag} => ${version}`);
103 | }
104 |
105 | release().catch((err) => {
106 | console.error(err);
107 | process.exit(1);
108 | });
109 |
--------------------------------------------------------------------------------
/docs/CUSTOM_EXTENSION.md:
--------------------------------------------------------------------------------
1 | ## Writing your own GraphQL-CLI Extension
2 |
3 | `graphql-cli` allow you to write your own plugin/extenion, and intergrate external tools and configuration, and run it from a single CLI.
4 |
5 | The current implementation of `graphql-cli` is using [Yargs](https://yargs.js.org/) to manage it's CLI commands.
6 |
7 | Plugins and extension are treated as NodeJS module by the `graphql-cli`, so it means you can use JavaScript/TypeScript/Any other super-set of JavaScript to write your extension. It means that you plugin will be loaded by it's name under `node_modules` - for example `graphql-cli my-custom-plugin ...`.
8 |
9 | `graphql-cli` also supports `graphql-config`, so it can help you easily load your GraphQL schema, operations and configuration from a unified config file.
10 |
11 | > If you are wrapping an existing tool that has it's own CLI already, consider to expose a programatic API so it will be easier to consume.
12 |
13 | ### TL;DR
14 |
15 | We have a ready-to-use boilerplate for that purpose, [you can find it here](https://github.com/dotansimha/graphql-cli-plugin-example).
16 |
17 | Also, inside this repo, under `packages/commands` you can find a set of plugins implementation you can use as reference.
18 |
19 | ### Getting Started
20 |
21 | Start by creating a simple JavaScript/TypeScript project, according to your preference. Install `@graphql-cli/common` package and use `defineCommand` utility in your entry point (usually `index` file):
22 |
23 | ```ts
24 | import { defineCommand } from '@graphql-cli/common';
25 |
26 | export default defineCommand((api) => {
27 | return {};
28 | });
29 | ```
30 |
31 | To register your CLI command, give it a name first. Use the `command` property:
32 |
33 | ```ts
34 | export default defineCommand((api) => {
35 | return {
36 | command: 'my-plugin',
37 | async handler() {
38 | // code here
39 | },
40 | };
41 | });
42 | ```
43 |
44 | Now, your plugin will be avaiable to use with the following command: `graphql my-plugin`.
45 |
46 | You can also add custom validations, flags, default values and much more with Yargs. [You can read the documentation here](https://yargs.js.org/docs/#api-commandcmd-desc-module).
47 |
48 | ## Testing your plugin locally
49 |
50 | To test your plugin locally, install `graphql-cli` in your project as a `devDependency`, and run the following command:
51 |
52 | ```
53 | graphql ./src/index.js
54 | ```
55 |
56 | If you registerd sub-commands, you should be able to run those this way:
57 |
58 | ```
59 | graphql ./src/index.js do-something
60 | ```
61 |
62 | > The path should point to the entry point of your script, and if you are using TypeScript - point to the compile file.
63 |
64 | ## Loading GraphQL Schema
65 |
66 | To easily load GraphQL schema, you can use `graphql-config`:
67 |
68 | ```ts
69 | import { defineCommand } from '@graphql-cli/common';
70 |
71 | export default defineCommand((api) => {
72 | return {
73 | command: 'my-plugin',
74 | builder(build) {
75 | return build.options({
76 | project: {
77 | type: 'string',
78 | describe: 'Name of your project',
79 | },
80 | });
81 | },
82 | async handler(args) {
83 | // use graphql-config and find configuration
84 | const config = await api.useConfig();
85 | // pick project
86 | const project = args.project ? config.getProject(args.project) : config.getDefault();
87 | // get schema
88 | const schema = await config.getSchema();
89 | },
90 | };
91 | });
92 | ```
93 |
94 | If you are using `graphql-config` to define your configuration, and you wish to load your extenion config from it, do:
95 |
96 | ```ts
97 | type MyConfig = { ... };
98 |
99 | const extensionConfig = await config.extension('my-plugin');
100 | ```
101 |
102 | ## Error Handling
103 |
104 | If you wish to fail the execution of your plugin and report it back to GraphQL CLI host, simply throw an error:
105 |
106 | ```ts
107 | import { defineCommand } from '@graphql-cli/common';
108 |
109 | export default defineCommand(() => {
110 | return {
111 | command: 'check-if-missing',
112 | handler() {
113 | if (somethingIsMissing) {
114 | throw new Error(`Ooops, something is missing`);
115 | }
116 | },
117 | };
118 | });
119 | ```
120 |
--------------------------------------------------------------------------------
/packages/commands/init/src/features/codegen.ts:
--------------------------------------------------------------------------------
1 | import { prompt } from 'inquirer';
2 | import { Context, PackageManifest, ProjectType, FrontendType, BackendType, askForEnum } from '../common';
3 |
4 | export async function askForCodegen({ context, project }: { context: Context; project: PackageManifest }) {
5 | if (!context.graphqlConfig.extensions.codegen) {
6 | const { isCodegenAsked } = await prompt([
7 | {
8 | type: 'confirm',
9 | name: 'isCodegenAsked',
10 | message: 'Do you want to use GraphQL Code Generator?',
11 | default: true,
12 | },
13 | ]);
14 | if (isCodegenAsked) {
15 | project.addDependency('@graphql-cli/codegen');
16 | project.addScript('graphql:codegen', 'graphql codegen');
17 |
18 | context.graphqlConfig.extensions.codegen = {
19 | generates: {},
20 | };
21 | let codegenPlugins = new Set();
22 | if (context.type === ProjectType.FullStack || context.type === ProjectType.BackendOnly) {
23 | const backendType = await askForEnum({
24 | enum: BackendType,
25 | message: 'What type of backend do you use?',
26 | defaultValue: BackendType.TS,
27 | });
28 |
29 | switch (backendType) {
30 | case BackendType.TS:
31 | codegenPlugins.add('typescript');
32 | codegenPlugins.add('typescript-resolvers');
33 | break;
34 | case BackendType.Java:
35 | codegenPlugins.add('java');
36 | codegenPlugins.add('java-resolvers');
37 | break;
38 | case BackendType.Kotlin:
39 | codegenPlugins.add('java');
40 | codegenPlugins.add('java-kotlin');
41 | break;
42 | }
43 |
44 | const { backendGeneratedFile } = await prompt([
45 | {
46 | type: 'input',
47 | name: 'backendGeneratedFile',
48 | message: 'Where do you want to have generated backend code?',
49 | default: './generated-backend.ts',
50 | },
51 | ]);
52 |
53 | context.graphqlConfig.extensions.codegen.generates[backendGeneratedFile] = {
54 | plugins: [...codegenPlugins],
55 | };
56 | }
57 |
58 | if (context.type === ProjectType.FullStack || context.type === ProjectType.FrontendOnly) {
59 | const frontendType = await askForEnum({
60 | enum: FrontendType,
61 | message: 'What type of frontend do you use?',
62 | defaultValue: FrontendType.TSReactApollo,
63 | });
64 |
65 | switch (frontendType) {
66 | case FrontendType.TSReactApollo:
67 | codegenPlugins.add('typescript');
68 | codegenPlugins.add('typescript-react-apollo');
69 | break;
70 | case FrontendType.ApolloAngular:
71 | codegenPlugins.add('typescript');
72 | codegenPlugins.add('typescript-apollo-angular');
73 | break;
74 | case FrontendType.StencilApollo:
75 | codegenPlugins.add('typescript');
76 | codegenPlugins.add('typescript-stencil-apollo');
77 | break;
78 | case FrontendType.TSUrql:
79 | codegenPlugins.add('typescript');
80 | codegenPlugins.add('typescript-urql');
81 | break;
82 | case FrontendType.GraphQLRequest:
83 | codegenPlugins.add('typescript');
84 | codegenPlugins.add('typescript-graphql-request');
85 | break;
86 | case FrontendType.ApolloAndroid:
87 | codegenPlugins.add('java-apollo-android');
88 | break;
89 | }
90 |
91 | const { frontendGeneratedFile } = await prompt([
92 | {
93 | type: 'input',
94 | name: 'frontendGeneratedFile',
95 | message: 'Where do you want to have generated frontend code?',
96 | default: './generated-frontend.ts',
97 | },
98 | ]);
99 |
100 | context.graphqlConfig.extensions.codegen.generates[frontendGeneratedFile] = {
101 | plugins: [...codegenPlugins],
102 | };
103 | }
104 | for (const codegenPlugin of codegenPlugins) {
105 | project.addDependency('@graphql-codegen/' + codegenPlugin);
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/packages/commands/init/src/common.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { ensureFile, writeFileSync } from 'fs-extra';
3 | import { prompt } from 'inquirer';
4 | import fullName from 'fullname';
5 | import latestVersion from 'latest-version';
6 |
7 | type StandardEnum = {
8 | [id: string]: T | string;
9 | [nu: number]: string;
10 | };
11 |
12 | export async function askForEnum>(options: {
13 | enum: Enum;
14 | message: string;
15 | defaultValue?: T;
16 | ignoreList?: (T | string)[];
17 | }): Promise {
18 | let choices: (T | string)[];
19 | const enumValues = Object.values(options.enum);
20 | if (options.ignoreList) {
21 | choices = enumValues.filter((enumValue) => !options.ignoreList.includes(enumValue));
22 | } else {
23 | choices = enumValues;
24 | }
25 |
26 | const { answer } = await prompt<{ answer: T }>([
27 | {
28 | type: 'list',
29 | name: 'answer',
30 | message: options.message,
31 | choices,
32 | default: options.defaultValue,
33 | },
34 | ]);
35 | return answer;
36 | }
37 |
38 | export interface Context {
39 | name: string;
40 | path: string;
41 | type?: ProjectType;
42 | graphqlConfig: any;
43 | }
44 |
45 | export enum InitializationType {
46 | FromScratch = 'I want to create a new project from a GraphQL CLI Project Template.',
47 | ExistingOpenAPI = 'I have an existing project using OpenAPI/Swagger Schema Definition.',
48 | ExistingGraphQL = 'I have an existing project using GraphQL and want to add GraphQL CLI (run from project root).',
49 | }
50 |
51 | export enum ProjectType {
52 | FullStack = 'Full Stack',
53 | FrontendOnly = 'Frontend only',
54 | BackendOnly = 'Backend only',
55 | }
56 |
57 | export enum FrontendType {
58 | TSReactApollo = 'TypeScript React Apollo',
59 | ApolloAngular = 'Apollo Angular',
60 | StencilApollo = 'Stencil Apollo',
61 | TSUrql = 'TypeScript Urql',
62 | GraphQLRequest = 'GraphQL Request',
63 | ApolloAndroid = 'Apollo Android',
64 | Other = 'Other',
65 | }
66 |
67 | export enum BackendType {
68 | TS = 'TypeScript',
69 | Java = 'Java',
70 | Kotlin = 'Kotlin',
71 | Other = 'Other',
72 | }
73 |
74 | export type PackageManifest = ReturnType;
75 | export function managePackageManifest() {
76 | const packages = new Set();
77 | const scripts = new Map();
78 |
79 | return {
80 | addDependency(name: string) {
81 | packages.add(name);
82 | },
83 | addScript(name: string, script: string) {
84 | scripts.set(name, script);
85 | },
86 | async writePackage({
87 | path,
88 | name,
89 | initializationType,
90 | }: {
91 | path: string;
92 | name: string;
93 | initializationType: InitializationType;
94 | }) {
95 | let packageJson: any = {};
96 | const packageJsonPath = join(path, 'package.json');
97 |
98 | // Try to load existing package.json
99 | try {
100 | const importedPackageJson = require(packageJsonPath);
101 |
102 | packageJson = importedPackageJson || {};
103 | } catch (err) {}
104 |
105 | if (initializationType === InitializationType.FromScratch) {
106 | packageJson.private = true;
107 | packageJson.name = name;
108 |
109 | const author = await fullName();
110 | if (author) {
111 | packageJson.author = {
112 | name: author,
113 | };
114 | }
115 | }
116 |
117 | for (const [scriptName, scriptValue] of scripts) {
118 | if (!packageJson.scripts) {
119 | packageJson.scripts = {};
120 | }
121 |
122 | if (!packageJson.scripts[scriptName]) {
123 | packageJson.scripts[scriptName] = scriptValue;
124 | }
125 | }
126 |
127 | // Add dev dependencies
128 | packageJson.devDependencies = packageJson.devDependencies || {};
129 |
130 | for await (const npmDependency of packages) {
131 | if (!(npmDependency in packageJson.devDependencies)) {
132 | packageJson.devDependencies[npmDependency] = await latestVersion(npmDependency);
133 | }
134 | }
135 |
136 | await ensureFile(packageJsonPath);
137 | writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
138 | },
139 | };
140 | }
141 |
--------------------------------------------------------------------------------
/website/docs/custom-commands.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: custom-commands
3 | title: Custom commands
4 | sidebar_label: Custom commands
5 | ---
6 |
7 | ## Writing your own commands
8 |
9 | `graphql-cli` allow you to write your own plugin/extenion, and intergrate external tools and configuration, and run it from a single CLI.
10 |
11 | The current implementation of `graphql-cli` is using [Yargs](https://yargs.js.org/) to manage it's CLI commands.
12 |
13 | Plugins and extension are treated as NodeJS module by the `graphql-cli`, so it means you can use JavaScript/TypeScript/Any other super-set of JavaScript to write your extension. It means that you plugin will be loaded by it's name under `node_modules` - for example `graphql-cli my-custom-plugin ...`.
14 |
15 | `graphql-cli` also supports `graphql-config`, so it can help you easily load your GraphQL schema, operations and configuration from a unified config file.
16 |
17 | > If you are wrapping an existing tool that has it's own CLI already, consider to expose a programatic API so it will be easier to consume.
18 |
19 | ### TL;DR
20 |
21 | We have a ready-to-use boilerplate for that purpose, [you can find it here](https://github.com/dotansimha/graphql-cli-plugin-example).
22 |
23 | Also, inside this repo, under `packages/commands` you can find a set of plugins implementation you can use as reference.
24 |
25 | ### Getting Started
26 |
27 | Start by creating a simple JavaScript/TypeScript project, according to your preference. Install `@graphql-cli/common` package and use `defineCommand` utility in your entry point (usually `index` file):
28 |
29 | ```ts
30 | import { defineCommand } from '@graphql-cli/common';
31 |
32 | export default defineCommand((api) => {
33 | return {};
34 | });
35 | ```
36 |
37 | To register your CLI command, give it a name first. Use the `command` property:
38 |
39 | ```ts
40 | export default defineCommand((api) => {
41 | return {
42 | command: 'my-plugin',
43 | async handler() {
44 | // code here
45 | },
46 | };
47 | });
48 | ```
49 |
50 | Now, your plugin will be avaiable to use with the following command: `graphql my-plugin`.
51 |
52 | You can also add custom validations, flags, default values and much more with Yargs. [You can read the documentation here](https://yargs.js.org/docs/#api-commandcmd-desc-module).
53 |
54 | ## Testing your plugin locally
55 |
56 | To test your plugin locally, install `graphql-cli` in your project as a `devDependency`, and run the following command:
57 |
58 | ```
59 | graphql ./src/index.js
60 | ```
61 |
62 | If you registerd sub-commands, you should be able to run those this way:
63 |
64 | ```
65 | graphql ./src/index.js do-something
66 | ```
67 |
68 | > The path should point to the entry point of your script, and if you are using TypeScript - point to the compile file.
69 |
70 | ## Loading GraphQL Schema
71 |
72 | To easily load GraphQL schema, you can use `graphql-config`:
73 |
74 | ```ts
75 | import { defineCommand } from '@graphql-cli/common';
76 |
77 | export default defineCommand((api) => {
78 | return {
79 | command: 'my-plugin',
80 | builder(build) {
81 | return build.options({
82 | project: {
83 | type: 'string',
84 | describe: 'Name of your project',
85 | },
86 | });
87 | },
88 | async handler(args) {
89 | // use graphql-config and find configuration
90 | const config = await api.useConfig();
91 | // pick project
92 | const project = args.project ? config.getProject(args.project) : config.getDefault();
93 | // get schema
94 | const schema = await config.getSchema();
95 | },
96 | };
97 | });
98 | ```
99 |
100 | If you are using `graphql-config` to define your configuration, and you wish to load your extenion config from it, do:
101 |
102 | ```ts
103 | type MyConfig = { ... };
104 |
105 | const extensionConfig = await config.extension('my-plugin');
106 | ```
107 |
108 | ## Error Handling
109 |
110 | If you wish to fail the execution of your plugin and report it back to GraphQL CLI host, simply throw an error:
111 |
112 | ```ts
113 | import { defineCommand } from '@graphql-cli/common';
114 |
115 | export default defineCommand(() => {
116 | return {
117 | command: 'check-if-missing',
118 | handler() {
119 | if (somethingIsMissing) {
120 | throw new Error(`Ooops, something is missing`);
121 | }
122 | },
123 | };
124 | });
125 | ```
126 |
--------------------------------------------------------------------------------
/website/docs/serve.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: serve
3 | title: serve
4 | sidebar_label: serve
5 | ---
6 |
7 | Serves a full featured [GraphQL CRUD](https://graphqlcrud.org/) API with subscriptions and data synchronization running in just a few seconds without writing a single line of code - all you need is a data model `.graphql` file.
8 |
9 | GraphQL Serve is a CLI tool that leverages the power of Graphback to generate a codeless Node.js GraphQL API complete with schema and CRUD resolvers and an in-memory MongoDB database.
10 |
11 | ### Installation
12 |
13 | import Tabs from '@theme/Tabs';
14 | import TabItem from '@theme/TabItem';
15 |
16 |
23 |
24 |
25 | ```
26 | yarn global add @graphql-cli/serve
27 | ```
28 |
29 |
30 |
31 |
32 |
33 | ```
34 | npm i -g @graphql-cli/serve
35 | ```
36 |
37 |
38 |
39 |
40 | ### Usage
41 |
42 | The bare minimum you need is a GraphQL file with your data models. Create a file called `Note.graphql` and add the following:
43 |
44 | ```graphql
45 | """ @model """
46 | type Note {
47 | _id: GraphbackObjectID!
48 | title: String!
49 | description: String
50 | likes: Int
51 | }
52 |
53 | scalar GraphbackObjectID
54 | ```
55 |
56 | The `@model` annotation indicates that `Note` is a data model and Graphback will generate resolvers, a CRUD service and data source for it. You can learn how to build more complex data models in [Data Model](https://graphback.dev/docs/model/datamodel#model).
57 |
58 | #### Running your codeless GraphQL server
59 |
60 | To start your server, run the following command from the same directory as `Note.graphql`:
61 |
62 | ```bash
63 | graphql serve Note.graphql
64 | ```
65 |
66 | This will start a GraphQL server on a random port using the `Note.graphql` data models we just added.
67 |
68 | You can customise the directory of the data models:
69 |
70 | ```bash
71 | graphql serve ./path/to/models
72 | ```
73 |
74 | You can also specify where to load the data models from with a Glob pattern:
75 |
76 | ```bash
77 | graphql serve ./schema/**/*.graphql
78 | ```
79 |
80 | You can specify which port to start the server on:
81 |
82 | ```bash
83 | $ graphql serve ./path/to/models --port 8080
84 |
85 | Starting server...
86 |
87 | Listening at: http://localhost:8080/graphql
88 | ```
89 |
90 | ### Enable Data Synchronization
91 |
92 | GraphQL Serve can also operate on data sync models. Under the hood this uses the [Data Sync](https://graphback.dev/docs/datasync/intro) package.
93 | To enable data synchronization, all we need to do is enable datasync capabilities on our models via the `@datasync` annotation.
94 |
95 | For the `Note` model defined above, this would look like:
96 |
97 | ```graphql
98 | """
99 | @model
100 | @datasync
101 | """
102 | type Note {
103 | _id: GraphbackObjectID!
104 | title: String!
105 | description: String
106 | likes: Int
107 | }
108 |
109 | scalar GraphbackObjectID
110 | ```
111 |
112 | Once we have a model with datasync capabilities, we can run our GraphQL server by enabling data synchronization as shown below:
113 |
114 | ```bash
115 | graphql serve Note.graphql --datasync
116 | ```
117 |
118 | Conflict resolution strategies for datasync enabled models can be specified via the --conflict option:
119 |
120 | ```bash
121 | graphql serve Note.graphql --datasync --conflict=clientSideWins
122 | ```
123 |
124 | This defaults to ClientSideWins, if unset.
125 |
126 | The TTL for delta tables, can also be set using the --deltaTTL option:
127 |
128 | ```bash
129 | graphql serve Note.graphql --datasync --deltaTTL=172800
130 | ```
131 |
132 | This value defaults to `172800` when unused
133 |
134 |
135 | #### Arguments
136 |
137 | | argument | description | default |
138 | | --- | --- | --- |
139 | | `Model` | Directory to search for data models | `undefined` |
140 |
141 | #### Options
142 |
143 | | option | alias | description | default |
144 | | --- | --- | --- | --- |
145 | | `--port` | `-p` | Port on which to run the HTTP server | `Random port` |
146 | | `--datasync` | `--ds` | Enable datasynchronization features | `false` |
147 | | `--deltaTTL` | N/A | Specify a conflict resolution strategy with --datasync. Choices: `clientSideWins`, `serverSideWins`, `throwOnConflict` | `clientSideWins` |
148 | | `--conflict` | N/A | Specify a TTL for delta tables with --datasync | `172800` |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/templates/fullstack/client/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
19 | );
20 |
21 | type Config = {
22 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
23 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
24 | };
25 |
26 | export function register(config?: Config) {
27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
28 | // The URL constructor is available in all browsers that support SW.
29 | const publicUrl = new URL((process as { env: { [key: string]: string } }).env.PUBLIC_URL, window.location.href);
30 | if (publicUrl.origin !== window.location.origin) {
31 | // Our service worker won't work if PUBLIC_URL is on a different origin
32 | // from what our page is served on. This might happen if a CDN is used to
33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
34 | return;
35 | }
36 |
37 | window.addEventListener('load', () => {
38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
39 |
40 | if (isLocalhost) {
41 | // This is running on localhost. Let's check if a service worker still exists or not.
42 | checkValidServiceWorker(swUrl, config);
43 |
44 | // Add some additional logging to localhost, pointing developers to the
45 | // service worker/PWA documentation.
46 | navigator.serviceWorker.ready.then(() => {
47 | console.log(
48 | 'This web app is being served cache-first by a service ' +
49 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
50 | );
51 | });
52 | } else {
53 | // Is not localhost. Just register service worker
54 | registerValidSW(swUrl, config);
55 | }
56 | });
57 | }
58 | }
59 |
60 | function registerValidSW(swUrl: string, config?: Config) {
61 | navigator.serviceWorker
62 | .register(swUrl)
63 | .then((registration) => {
64 | registration.onupdatefound = () => {
65 | const installingWorker = registration.installing;
66 | if (installingWorker == null) {
67 | return;
68 | }
69 | installingWorker.onstatechange = () => {
70 | if (installingWorker.state === 'installed') {
71 | if (navigator.serviceWorker.controller) {
72 | // At this point, the updated precached content has been fetched,
73 | // but the previous service worker will still serve the older
74 | // content until all client tabs are closed.
75 | console.log(
76 | 'New content is available and will be used when all ' +
77 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
78 | );
79 |
80 | // Execute callback
81 | if (config && config.onUpdate) {
82 | config.onUpdate(registration);
83 | }
84 | } else {
85 | // At this point, everything has been precached.
86 | // It's the perfect time to display a
87 | // "Content is cached for offline use." message.
88 | console.log('Content is cached for offline use.');
89 |
90 | // Execute callback
91 | if (config && config.onSuccess) {
92 | config.onSuccess(registration);
93 | }
94 | }
95 | }
96 | };
97 | };
98 | })
99 | .catch((error) => {
100 | console.error('Error during service worker registration:', error);
101 | });
102 | }
103 |
104 | function checkValidServiceWorker(swUrl: string, config?: Config) {
105 | // Check if the service worker can be found. If it can't reload the page.
106 | fetch(swUrl)
107 | .then((response) => {
108 | // Ensure service worker exists, and that we really are getting a JS file.
109 | const contentType = response.headers.get('content-type');
110 | if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then((registration) => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log('No internet connection found. App is running in offline mode.');
124 | });
125 | }
126 |
127 | export function unregister() {
128 | if ('serviceWorker' in navigator) {
129 | navigator.serviceWorker.ready.then((registration) => {
130 | registration.unregister();
131 | });
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/packages/commands/init/src/index.ts:
--------------------------------------------------------------------------------
1 | import { defineCommand } from '@graphql-cli/common';
2 | import { prompt } from 'inquirer';
3 | import { join } from 'path';
4 | import chalk from 'chalk';
5 | import { ensureFile, writeFileSync, readFileSync, existsSync } from 'fs-extra';
6 | import { safeLoad as YAMLParse, safeDump as YAMLStringify } from 'js-yaml';
7 | import { Context, InitializationType, ProjectType, askForEnum, managePackageManifest } from './common';
8 | import { askForInspector } from './features/inspector';
9 | import { askForCodegen } from './features/codegen';
10 | import { fromExisting } from './sources/from-existing';
11 | import { fromScratch } from './sources/from-scratch';
12 | import { fromExistingOpenAPI } from './sources/from-open-api';
13 |
14 | export default defineCommand<
15 | {},
16 | {
17 | projectName?: string;
18 | templateName?: string;
19 | templateUrl?: string;
20 | }
21 | >(() => {
22 | return {
23 | command: 'init',
24 | builder(yargs) {
25 | return yargs.options({
26 | projectName: {
27 | describe: 'Project name',
28 | type: 'string',
29 | },
30 | templateName: {
31 | describe: 'Name of the predefined template',
32 | type: 'string',
33 | },
34 | templateUrl: {
35 | describe: 'GitHub URL of the template. For example (http://github.com/example/graphql-cli-example-template)',
36 | type: 'string',
37 | },
38 | });
39 | },
40 | async handler(args) {
41 | let { projectName, templateName, templateUrl } = args;
42 | const initializationType = await askForInitializationType();
43 |
44 | const context: Context = {
45 | name: projectName,
46 | path: process.cwd(),
47 | graphqlConfig: {
48 | extensions: {},
49 | },
50 | };
51 |
52 | const project = managePackageManifest();
53 |
54 | project.addDependency('graphql-cli');
55 |
56 | if (initializationType === InitializationType.ExistingGraphQL) {
57 | await fromExisting({
58 | context,
59 | project,
60 | });
61 | } else if (initializationType === InitializationType.FromScratch) {
62 | await fromScratch({
63 | context,
64 | templateName,
65 | templateUrl,
66 | });
67 | }
68 |
69 | if (initializationType !== InitializationType.FromScratch) {
70 | context.type = await askForProject();
71 | }
72 |
73 | loadGraphQLConfig(context);
74 |
75 | if (initializationType === InitializationType.ExistingOpenAPI) {
76 | await fromExistingOpenAPI(context);
77 | }
78 |
79 | await askForSchema(context);
80 | await askForDocuments(context);
81 | await askForCodegen({ context, project });
82 | await askForInspector({ context, project });
83 |
84 | await Promise.all([
85 | writeGraphQLConfig(context),
86 | project.writePackage({
87 | path: context.path,
88 | name: projectName,
89 | initializationType,
90 | })]);
91 |
92 | const successMessages = [
93 | `🚀 GraphQL CLI project successfully initialized:`,
94 | context.name,
95 | 'Next Steps:',
96 | `- Change directory to the project folder - ${chalk.cyan(`cd ${context.path}`)}`,
97 | `- Run ${chalk.cyan(`yarn install`)} to install dependencies`,
98 | ];
99 |
100 | if (initializationType !== InitializationType.ExistingGraphQL) {
101 | successMessages.push(
102 | `- ${chalk.cyan(`(Optional)`)} Initialize your git repo. ${chalk.cyan(`git init`)}.`,
103 | `- Follow the instructions in README.md to continue...`
104 | );
105 | }
106 |
107 | console.info(successMessages.join('\n'));
108 | process.exit(0);
109 | },
110 | };
111 | });
112 |
113 | function askForProject() {
114 | return askForEnum({
115 | enum: ProjectType,
116 | message: 'What is the type of the project?',
117 | defaultValue: ProjectType.FullStack,
118 | });
119 | }
120 |
121 | function askForInitializationType() {
122 | return askForEnum({
123 | enum: InitializationType,
124 | message: 'Select the best option for you',
125 | defaultValue: InitializationType.FromScratch,
126 | ignoreList: [],
127 | });
128 | }
129 |
130 | function loadGraphQLConfig(context: Context) {
131 | const graphqlConfigPath = join(context.path, '.graphqlrc.yml');
132 |
133 | try {
134 | if (existsSync(graphqlConfigPath)) {
135 | context.graphqlConfig = YAMLParse(readFileSync(graphqlConfigPath, 'utf8'));
136 | }
137 | } catch (e) {
138 | console.warn(`Existing GraphQL Config file looks broken! Skipping...`);
139 | }
140 | }
141 |
142 | async function askForSchema(context: Context) {
143 | if (!context.graphqlConfig.schema) {
144 | const { schema } = await prompt([
145 | {
146 | type: 'input',
147 | name: 'schema',
148 | message: 'Where is your schema?',
149 | default: './schema.graphql',
150 | },
151 | ]);
152 |
153 | context.graphqlConfig.schema = schema.endsWith('.ts')
154 | ? {
155 | [schema]: {
156 | require: 'ts-node/register',
157 | },
158 | }
159 | : schema;
160 | }
161 | }
162 |
163 | async function askForDocuments(context: Context) {
164 | if (
165 | !context.graphqlConfig.documents &&
166 | (context.type === ProjectType.FullStack || context.type === ProjectType.FrontendOnly)
167 | ) {
168 | const { documents } = await prompt([
169 | {
170 | type: 'input',
171 | name: 'documents',
172 | message: 'Where are your operation documents?',
173 | },
174 | ]);
175 | context.graphqlConfig.documents = documents;
176 | }
177 | }
178 |
179 | async function writeGraphQLConfig(context: Context) {
180 | const configPath = join(context.path, '.graphqlrc.yml');
181 | await ensureFile(configPath);
182 |
183 | const keys = ['schema', 'documents', 'extensions'];
184 |
185 | function sortKeys(a: string, b: string) {
186 | const ai = keys.indexOf(a);
187 | const bi = keys.indexOf(b);
188 |
189 | if (ai === -1 && bi === -1) {
190 | return a.localeCompare(b);
191 | }
192 |
193 | return ai <= bi ? -1 : 1;
194 | }
195 |
196 | writeFileSync(
197 | configPath,
198 | YAMLStringify(context.graphqlConfig, {
199 | sortKeys,
200 | })
201 | );
202 | }
203 |
--------------------------------------------------------------------------------
/website/algolia-lockfile.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "objectID": "cli-codegen",
4 | "headings": [],
5 | "toc": [],
6 | "content": "-",
7 | "url": "https://www.graphql-cli.com/codegen",
8 | "domain": "https://www.graphql-cli.com/",
9 | "hierarchy": ["CLI", "codegen"],
10 | "source": "CLI",
11 | "title": "codegen",
12 | "type": "Documentation"
13 | },
14 | {
15 | "objectID": "cli-coverage",
16 | "headings": [],
17 | "toc": [],
18 | "content": "-",
19 | "url": "https://www.graphql-cli.com/coverage",
20 | "domain": "https://www.graphql-cli.com/",
21 | "hierarchy": ["CLI", "coverage"],
22 | "source": "CLI",
23 | "title": "coverage",
24 | "type": "Documentation"
25 | },
26 | {
27 | "objectID": "cli-custom-commands",
28 | "headings": [
29 | "Writing your own commands",
30 | "Testing your plugin locally",
31 | "Loading GraphQL Schema",
32 | "Error Handling"
33 | ],
34 | "toc": [
35 | {
36 | "children": [
37 | {
38 | "children": [
39 | {
40 | "children": [],
41 | "title": "Getting Started",
42 | "anchor": "getting-started"
43 | }
44 | ],
45 | "title": "TL;DR",
46 | "anchor": "tldr"
47 | }
48 | ],
49 | "title": "Writing your own commands",
50 | "anchor": "writing-your-own-commands"
51 | },
52 | {
53 | "children": [],
54 | "title": "Testing your plugin locally",
55 | "anchor": "testing-your-plugin-locally"
56 | },
57 | {
58 | "children": [],
59 | "title": "Loading GraphQL Schema",
60 | "anchor": "loading-graphql-schema"
61 | },
62 | {
63 | "children": [],
64 | "title": "Error Handling",
65 | "anchor": "error-handling"
66 | }
67 | ],
68 | "content": "-",
69 | "url": "https://www.graphql-cli.com/custom-commands",
70 | "domain": "https://www.graphql-cli.com/",
71 | "hierarchy": ["CLI", "Custom commands"],
72 | "source": "CLI",
73 | "title": "Custom commands",
74 | "type": "Documentation"
75 | },
76 | {
77 | "objectID": "cli-diff",
78 | "headings": [],
79 | "toc": [],
80 | "content": "-",
81 | "url": "https://www.graphql-cli.com/diff",
82 | "domain": "https://www.graphql-cli.com/",
83 | "hierarchy": ["CLI", "diff"],
84 | "source": "CLI",
85 | "title": "diff",
86 | "type": "Documentation"
87 | },
88 | {
89 | "objectID": "cli-discover",
90 | "headings": [],
91 | "toc": [],
92 | "content": "-",
93 | "url": "https://www.graphql-cli.com/discover",
94 | "domain": "https://www.graphql-cli.com/",
95 | "hierarchy": ["CLI", "discover"],
96 | "source": "CLI",
97 | "title": "discover",
98 | "type": "Documentation"
99 | },
100 | {
101 | "objectID": "cli-generate",
102 | "headings": [],
103 | "toc": [],
104 | "content": "-",
105 | "url": "https://www.graphql-cli.com/generate",
106 | "domain": "https://www.graphql-cli.com/",
107 | "hierarchy": ["CLI", "generate"],
108 | "source": "CLI",
109 | "title": "generate",
110 | "type": "Documentation"
111 | },
112 | {
113 | "objectID": "cli-init",
114 | "headings": [],
115 | "toc": [],
116 | "content": "-",
117 | "url": "https://www.graphql-cli.com/init",
118 | "domain": "https://www.graphql-cli.com/",
119 | "hierarchy": ["CLI", "init"],
120 | "source": "CLI",
121 | "title": "init",
122 | "type": "Documentation"
123 | },
124 | {
125 | "objectID": "cli-introduction",
126 | "headings": [],
127 | "toc": [],
128 | "content": "-",
129 | "url": "https://www.graphql-cli.com/introduction",
130 | "domain": "https://www.graphql-cli.com/",
131 | "hierarchy": ["CLI", "Introduction"],
132 | "source": "CLI",
133 | "title": "Introduction",
134 | "type": "Documentation"
135 | },
136 | {
137 | "objectID": "cli-introspect",
138 | "headings": [],
139 | "toc": [],
140 | "content": "-",
141 | "url": "https://www.graphql-cli.com/introspect",
142 | "domain": "https://www.graphql-cli.com/",
143 | "hierarchy": ["CLI", "introspect"],
144 | "source": "CLI",
145 | "title": "introspect",
146 | "type": "Documentation"
147 | },
148 | {
149 | "objectID": "cli-migration",
150 | "headings": ["Migration from GraphQL CLI 3.x or older"],
151 | "toc": [
152 | {
153 | "children": [
154 | {
155 | "children": [
156 | {
157 | "children": [],
158 | "title": "Update your configuration file",
159 | "anchor": "update-your-configuration-file"
160 | },
161 | {
162 | "children": [],
163 | "title": "Comparison of old commands",
164 | "anchor": "comparison-of-old-commands"
165 | },
166 | {
167 | "children": [],
168 | "title": "Special Notes for Prisma users",
169 | "anchor": "special-notes-for-prisma-users"
170 | }
171 | ],
172 | "title": "Install the new version",
173 | "anchor": "install-the-new-version"
174 | }
175 | ],
176 | "title": "Migration from GraphQL CLI 3.x or older",
177 | "anchor": "migration-from-graphql-cli-3x-or-older"
178 | }
179 | ],
180 | "content": "-",
181 | "url": "https://www.graphql-cli.com/migration",
182 | "domain": "https://www.graphql-cli.com/",
183 | "hierarchy": ["CLI", "Migration"],
184 | "source": "CLI",
185 | "title": "Migration",
186 | "type": "Documentation"
187 | },
188 | {
189 | "objectID": "cli-serve",
190 | "headings": [],
191 | "toc": [],
192 | "content": "-",
193 | "url": "https://www.graphql-cli.com/serve",
194 | "domain": "https://www.graphql-cli.com/",
195 | "hierarchy": ["CLI", "serve"],
196 | "source": "CLI",
197 | "title": "serve",
198 | "type": "Documentation"
199 | },
200 | {
201 | "objectID": "cli-similar",
202 | "headings": [],
203 | "toc": [],
204 | "content": "-",
205 | "url": "https://www.graphql-cli.com/similar",
206 | "domain": "https://www.graphql-cli.com/",
207 | "hierarchy": ["CLI", "similar"],
208 | "source": "CLI",
209 | "title": "similar",
210 | "type": "Documentation"
211 | },
212 | {
213 | "objectID": "cli-validate",
214 | "headings": [],
215 | "toc": [],
216 | "content": "-",
217 | "url": "https://www.graphql-cli.com/validate",
218 | "domain": "https://www.graphql-cli.com/",
219 | "hierarchy": ["CLI", "validate"],
220 | "source": "CLI",
221 | "title": "validate",
222 | "type": "Documentation"
223 | }
224 | ]
225 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQL CLI
2 |
3 | 
4 |
5 | 
6 | [](https://npmjs.org/package/graphql-cli "View this project on npm") [](https://the-guild.dev/discord)
7 |
8 | Help us to improve new GraphQL CLI. Check out the new structure and commands below!
9 | Feel free to contact us in Discord channel. We would love to hear your feedback.
10 |
11 | ## Features
12 |
13 | - Helpful commands to improve your workflows
14 | - Compatible with editors and IDEs based on [`graphql-config`](https://github.com/kamilkisiela/graphql-config)
15 | - Powerful plugin system to extend `graphql-cli` with custom commands
16 |
17 | ## Install
18 |
19 | You can install the CLI using `yarn` by running the following command. This will add the `graphql` binary to your path.
20 |
21 | ```sh
22 | yarn global add graphql-cli
23 | ```
24 |
25 | The equivalent npm global install will also work.
26 |
27 | ## Migration from 3.x.x to 4.x.x
28 |
29 | **Important: many aspects of GraphQL CLI syntax and structure have changed in 4.x.x.** Please check out the [Migration Guide](./docs/MIGRATION.md) to learn more.
30 |
31 | ## Usage / Initialization
32 |
33 | At the heart of a project created using GraphQL CLI is the GraphQL Config configuration file. For starters, this configuration lets the cd CLI tools know where all of the GraphQL documents and operations are. For more information about GraphQL Config, [you can click here to learn more](https://graphql-config.com/docs/introduction).
34 |
35 | The most straightforward way to launch a GraphQL CLI-capable project with a working GraphQL Config setup is to use the `init` command from your desired workspace:
36 |
37 | ```sh
38 | npx graphql-cli init
39 | ```
40 |
41 | After a series of questions from the command-prompt, the system will use the inputs and selected project templates to generate a working project complete with a GraphQL Config setup. The GraphQL Config file is generated referencing the necessary files and ecosystem plugins.
42 |
43 | You can also get started with GraphQL CLI by creating your own GraphQL Config file using an editor of your choice. Starting with a filename `.graphqlrc.yml`, for instance, we could add:
44 |
45 | ```yml
46 | schema: "server/src/schema/**/*.graphql"
47 | documents: "client/src/documents/**/*.graphql"
48 | ```
49 |
50 | This is now a valid YAML-syntax GraphQL Config file. Using `init` from the GraphQL CLI will generate a project based on the instructions in your YAML.
51 |
52 | Finally, one of the options with `graphql init` is to access schema using an OpenAPI or Swagger endpoint. Choose this option at the start of the Init question tree, and then follow the instructions to navigate to the URL of your choice.
53 |
54 | ## Plugin System
55 |
56 | Each command in GraphQL CLI is a seperate package, so you can have your own plugins or use the ones we maintain. You can have those commands by installing them like `@graphql-cli/[COMMAND-NAME]`.
57 |
58 | To configure a command/plugin, you need to update the `extensions` field in your GraphQL Config file (`.graphqlrc.yml`). See `extensions:` in the example below.
59 |
60 | ```yml
61 | schema:
62 | ./server/src/schema/**/*.ts:
63 | require: ts-node/register
64 | documents: ./client/src/graphql/**/*.ts
65 | extensions:
66 | codegen:
67 | generates:
68 | ./server/src/generated-types.d.ts:
69 | plugins:
70 | - typescript
71 | - typescript-resolvers
72 | ./client/src/generated-types.tsx:
73 | plugins:
74 | - typescript
75 | - typescript-operations
76 | - typescript-react-apollo
77 | config:
78 | withHooks: true
79 | graphback:
80 | model: './model/*.graphql'
81 | plugins:
82 | graphback-schema:
83 | outputPath: './src/schema/schema.graphql'
84 | ...
85 | ```
86 |
87 | [For a detailed example check out a template file here.](https://github.com/Urigo/graphql-cli/blob/master/templates/fullstack/.graphqlrc.yml)
88 |
89 | Some of the available Plugins are:
90 |
91 | - [`init`](https://github.com/Urigo/graphql-cli/tree/focs/packages/commands/init) - Creates a GraphQL project using a template or GraphQL Config file for your existing project.
92 | - [`codegen`](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/graphql-cli-codegen-plugin) - GraphQL Code Generator's GraphQL CLI plugin. GraphQL Code Generator is a tool that generates code from your GraphQL schema and documents for your backend or frontend with flexible support for custom plugins and templates. [Learn More](https://graphql-code-generator.com)
93 | - [`generate`](https://github.com/Urigo/graphql-cli/tree/master/packages/commands/generate) - Generate schema and client-side documents for your GraphQL project by using [Graphback](https://graphback.dev).
94 | - [`coverage`](https://github.com/kamilkisiela/graphql-inspector/tree/master/packages/graphql-cli/coverage) - Schema coverage based on documents. Find out how many times types and fields are used in your application using [GraphQL Inspector](https://graphql-inspector.com/docs/essentials/coverage).
95 | - [`diff`](https://github.com/kamilkisiela/graphql-inspector/tree/master/packages/graphql-cli/diff) - Compares schemas and finds breaking or dangerous changes using [GraphQL Inspector](https://graphql-inspector.com/docs/essentials/diff).
96 | - You can also compare your current schema against a base schema using URL, Git link and local file. You can give this pointer in the command line after `graphql diff` or in GraphQL Config file:
97 |
98 | ```yml
99 | # ...
100 | extensions:
101 | diff:
102 | baseSchema: git:origin/master:schema.graphql
103 | ```
104 |
105 | - [`similar`]((https://github.com/kamilkisiela/graphql-inspector/tree/master/packages/graphql-cli/similar)) - Get a list of similar types in order to find duplicates using [GraphQL Inspector](https://graphql-inspector.com/docs/essentials/similar).
106 | - [`validate`]((https://github.com/kamilkisiela/graphql-inspector/tree/master/packages/graphql-cli/validate)) - Validates documents against a schema and looks for deprecated usage using [GraphQL Inspector](https://graphql-inspector.com/docs/essentials/validate).
107 | - [`serve`](https://github.com/Urigo/graphql-cli/tree/master/packages/commands/serve) - Serves a GraphQL server, using an in memory database and a defined GraphQL schema. Please read through [serve documentation](./website/docs/command-serve.md) to learn more about this command.
108 |
109 | More plugins are definitely welcome! Please check the existing ones to see how to use GraphQL Config and GraphQL CLI API.
110 |
111 | ## Contributing
112 |
113 | Please read through the [contributing guidelines](./CONTRIBUTING.md)
114 |
115 | ## Writing your own plugin
116 |
117 | GraphQL CLI supports custom plugins, [you can find a tutorial and example here](./docs/CUSTOM_EXTENSION.md)
118 |
119 | ## Help & Community [](https://the-guild.dev/discord)
120 |
121 | Join our [Discord chat](https://the-guild.dev/discord) if you run into issues or have questions. We're excited to welcome you to the community!
122 |
--------------------------------------------------------------------------------
/docs/MIGRATION.md:
--------------------------------------------------------------------------------
1 | # Migration from GraphQL CLI 3.x or older
2 |
3 | Starting with GraphQL CLI 4.0 and higher, the way projects are set up is significantly restructured.
4 |
5 | ## Install the new version
6 | To get started, install the new version:
7 | ```sh
8 | yarn global add graphql-cli
9 | ```
10 | You can also globally install using npm.
11 |
12 | > NOTE: If you have previous version of the GraphQL-CLI installed make sure to uninstall it first
13 |
14 | ```bash
15 | npm uninstall graphql-cli
16 | ```
17 |
18 | ## Update your configuration file
19 | If you are working from an existing project, the GraphQL Config file that is used by GraphQL CLI is now called `.graphqlrc.yml` (by default) instead of `.graphqlconfig`. [Other options exist](https://graphql-config.com/usage) for naming the config files supported by GraphQL CLI, but this guide will assume you're using YAML syntax.
20 |
21 | To migrate, you will first need to update your GraphQL Configuration file to match GraphQL Config's updated syntax and structure.
22 |
23 | You can [check here](https://graphql-config.com/usage) for more information about the new structure.
24 |
25 | ###Specifying schema(s):
26 |
27 | ```yml
28 | schema: ./src/schema/**/*.graphql #You can have URL endpoint, Git URL and local files using globs here.
29 | ```
30 |
31 | `schemaPath` is replaced by `schema`, which is now more flexible then the previous approach. This field is used by all commands and plugins of GraphQL CLI.
32 |
33 | ## Comparison of old commands
34 |
35 | ### `get-schema` is no longer available
36 | In previous versions, you were able to download the schema to the given path in `schemaPath` from the URL given inside `endpoint`. In the new version, `schema` refers to the endpoint of the schema.
37 |
38 | If you use Prisma or any other tool that provides your schema under URL endpoint, you must specify it using the following syntax in your configuration YAML:
39 |
40 | ```yaml
41 | schema: http://localhost:4000/graphql #This is the schema path
42 | ```
43 |
44 | If you want to download the schema from this URL to your local file system, you will also need to install `codegen` plugin and its `schema-ast` plugin using the following command or its npm equivalent:
45 |
46 | ```bash
47 | yarn add @graphql-cli/codegen @graphql-codegen/schema-ast --dev
48 | ```
49 |
50 | After that, you can specify the output path of the local schema file:
51 |
52 | ```yaml
53 | schema: http://localhost:4000/graphql
54 | extensions:
55 | codegen:
56 | generates:
57 | ./schema.graphql:
58 | plugins:
59 | - schema-ast
60 | ```
61 |
62 | By running `graphql codegen`, the `schema.graphql` file is generated in the root path of your project.
63 |
64 | #### For JSON Output
65 | If you want to download the schema as a `json` introspection file, you will need to install `@graphql-codegen/introspection` instead, and add `introspection` instead of `schema-ast`.
66 |
67 | ```yaml
68 | schema: http://localhost:4000/graphql
69 | extensions:
70 | codegen:
71 | generates:
72 | ./schema.json:
73 | plugins:
74 | - introspection
75 | ```
76 |
77 | ### `create` is no longer available: it is replaced by the `init` command.
78 | If you want to create a GraphQL Config file on an existing project or create a project using a template from scratch, you can use `graphql init` command.
79 | This command will ask some questions about your new or existing project to update your dependencies and create a new configuration file for GraphQL CLI.
80 |
81 | ### `diff` has been changed
82 | If you want to see the differences between your schema and another schema, use the `diff` command as follows:
83 | ```yaml
84 | graphql diff git:origin/master:schema.graphql
85 | ```
86 | For example, `diff` will show the differences between the version in `schema` field of GraphQL Configuration file and the `schema.graphql` file in the remote master branch.
87 |
88 | Alternatively, you can compare `schema` with a URL endpoint:
89 | ```yaml
90 | graphql diff http://my-dev-instance.com/graphql
91 | ```
92 |
93 | ### `add-endpoint`, `add-project`, `schema-status`, `ping`, `query`, `prepare`, `lint` and `playground` commands are no longer available.
94 | GraphQL CLI (as well as GraphQL Config) no longer separates `endpoints` and `schemaPath`. The new `schema` field refers to the single endpoint of your GraphQL schema, so it can be a URL endpoint or a local file. If your project uses a remote schema, you can directly define this URL in `schema` path without downloading it or defining it as an extra `endpoint` etc.
95 |
96 | Instead of using these legacy commands, you can create a faked server to test your schema using the `yarn serve` command.
97 |
98 | ### `codegen` now uses GraphQL Code Generator
99 | GraphQL CLI now uses GraphQL Code Generator which has a lot of plugins and templates for various environments, platforms and use cases. You can generate resolver signatures, TypeScript representations of your GraphQL Schema and more. [Check it out](https://graphql-code-generator.com/)
100 |
101 | The usage is slightly different from the old one:
102 | ```yaml
103 | schema: src/schema/**/*.graphql
104 | extensions:
105 | codegen:
106 | src/generated-types.ts: # Output file name
107 | - typescript # Plugin names to be used
108 | - typescript-resolvers
109 | ```
110 |
111 | For instance, consider a hypothetical case where you need to generate TypeScript resolvers signatures for your GraphQL project. To do this, you would install the `codegen` plugin and the additional plugins and templates for GraphQL Code Generator. For this case, you would need `typescript` and `typescript-resolvers` plugins:
112 |
113 | ```bash
114 | yarn add @graphql-cli/codegen @graphql-codegen/typescript @graphql-codegen/typescript-resolvers --dev
115 | ```
116 |
117 | Now, using a single command, you can run GraphQL Code Generator using GraphQL CLI:
118 | ```bash
119 | graphql codegen
120 | ```
121 |
122 | ## Special Notes for Prisma users
123 | Prisma users will need to download a schema from a URL endpoint. For example, here is a *legacy GraphQL Config file* doing this:
124 |
125 | `.graphqlconfig`
126 | ```yaml
127 | projects:
128 | app:
129 | schemaPath: src/schema.graphql
130 | extensions:
131 | endpoints:
132 | default: http://localhost:4000
133 | database:
134 | schemaPath: src/generated/prisma-client/prisma.graphql
135 | extensions:
136 | prisma: prisma/prisma.yml
137 | ```
138 |
139 | **To update the configuration for the new GraphQL CLI** you need to rename the file to `.graphqlrc.yml`, and then update the file as follows:
140 |
141 | `.graphqlrc.yml`
142 | ```yaml
143 | projects:
144 | app:
145 | schema: src/schema.graphql
146 | database:
147 | schema: prisma/prisma.yml
148 | extensions:
149 | codegen:
150 | generates:
151 | ./src/generated/prisma-client/prisma.graphql:
152 | plugins:
153 | - schema-ast
154 | ```
155 |
156 | You can directly point to your `prisma.yml` file instead of the URL endpoint.
157 |
158 | Before running the GraphQL CLI command to use this new configuration, make sure you have installed the `@graphql-cli/codegen` and `@graphql-codegen/schema-ast` plugins using:
159 | ```sh
160 | yarn add @graphql-cli/codegen @graphql-codegen/schema-ast --dev
161 | ```
162 |
163 | Now you can run `graphql codegen --project database` for generating your `prisma.graphql` file.
164 |
165 | You will also need to update your `prisma.yml` file if you're using `graphql get-schema` with Prisma:
166 | ```yaml
167 | ...
168 | # Ensures Prisma client is re-generated after a datamodel change.
169 | hooks:
170 | post-deploy:
171 | - graphql codegen --project database # instead of graphql get-schema
172 | - prisma generate
173 | ```
174 |
--------------------------------------------------------------------------------
/website/docs/migration.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: migration
3 | title: Migration
4 | sidebar_label: Migration
5 | ---
6 |
7 | ## Migration from GraphQL CLI 3.x or older
8 |
9 | Starting with GraphQL CLI 4.0 and higher, the way projects are set up is significantly restructured.
10 |
11 | ### Install the new version
12 | To get started, install the new version:
13 | ```sh
14 | yarn global add graphql-cli
15 | ```
16 | You can also globally install using npm.
17 |
18 | > NOTE: If you have previous version of the GraphQL-CLI installed make sure to uninstall it first
19 |
20 | ```bash
21 | npm uninstall graphql-cli
22 | ```
23 |
24 | ### Update your configuration file
25 | If you are working from an existing project, the GraphQL Config file that is used by GraphQL CLI is now called `.graphqlrc.yml` (by default) instead of `.graphqlconfig`. [Other options exist](https://graphql-config.com/usage) for naming the config files supported by GraphQL CLI, but this guide will assume you're using YAML syntax.
26 |
27 | To migrate, you will first need to update your GraphQL Configuration file to match GraphQL Config's updated syntax and structure.
28 |
29 | You can [check here](https://graphql-config.com/usage) for more information about the new structure.
30 |
31 | ####Specifying schema(s):
32 |
33 | ```yml
34 | schema: ./src/schema/**/*.graphql #You can have URL endpoint, Git URL and local files using globs here.
35 | ```
36 |
37 | `schemaPath` is replaced by `schema`, which is now more flexible then the previous approach. This field is used by all commands and plugins of GraphQL CLI.
38 |
39 | ### Comparison of old commands
40 |
41 | #### `get-schema` is no longer available
42 | In previous versions, you were able to download the schema to the given path in `schemaPath` from the URL given inside `endpoint`. In the new version, `schema` refers to the endpoint of the schema.
43 |
44 | If you use Prisma or any other tool that provides your schema under URL endpoint, you must to specify it using the following syntax in your configuration YAML:
45 |
46 | ```yaml
47 | schema: http://localhost:4000/graphql #This is the schema path
48 | ```
49 |
50 | If you want to download the schema from this URL to your local file system, you will also need to install `codegen` plugin and its `schema-ast` plugin using the following command or its npm equivalent:
51 |
52 | ```bash
53 | yarn add @graphql-cli/codegen @graphql-codegen/schema-ast --dev
54 | ```
55 |
56 | After that, you can specify the output path of the local schema file:
57 |
58 | ```yaml
59 | schema: http://localhost:4000/graphql
60 | extensions:
61 | codegen:
62 | generates:
63 | ./schema.graphql:
64 | plugins:
65 | - schema-ast
66 | ```
67 |
68 | By running `graphql codegen`, the `schema.graphql` file is generated in the root path of your project.
69 |
70 | ##### For JSON Output
71 | If you want to download the schema as a `json` introspection file, you will need to install `@graphql-codegen/introspection` instead, and add `introspection` instead of `schema-ast`.
72 |
73 | ```yaml
74 | schema: http://localhost:4000/graphql
75 | extensions:
76 | codegen:
77 | generates:
78 | ./schema.json:
79 | plugins:
80 | - introspection
81 | ```
82 |
83 | #### `create` is no longer available: it is replaced by the `init` command.
84 | If you want to create a GraphQL Config file on an existing project or create a project using a template from scratch, you can use `graphql init` command.
85 | This command will ask some questions about your new or existing project to update your dependencies and create a new configuration file for GraphQL CLI.
86 |
87 | #### `diff` has been changed
88 | If you want to see the differences between your schema and another schema, use the `diff` command as follows:
89 | ```yaml
90 | graphql diff git:origin/master:schema.graphql
91 | ```
92 | For example, `diff` will show the differences between the version in `schema` field of GraphQL Configuration file and the `schema.graphql` file in the remote master branch.
93 |
94 | Alternatively, you can compare `schema` with a URL endpoint:
95 | ```yaml
96 | graphql diff http://my-dev-instance.com/graphql
97 | ```
98 |
99 | #### `add-endpoint`, `add-project`, `schema-status`, `ping`, `query`, `prepare`, `lint` and `playground` commands are no longer available.
100 | GraphQL CLI (as well as GraphQL Config) no longer separates `endpoints` and `schemaPath`. The new `schema` field refers to the single endpoint of your GraphQL schema, so it can be a URL endpoint or a local file. If your project uses a remote schema, you can directly define this URL in `schema` path without downloading it or defining it as an extra `endpoint` etc.
101 |
102 | Instead of using these legacy commands, you can create a faked server to test your schema using the `yarn serve` command.
103 |
104 | #### `codegen` now uses GraphQL Code Generator
105 | GraphQL CLI now uses GraphQL Code Generator which has a lot of plugins and templates for various environments, platforms and use cases. You can generate resolver signatures, TypeScript representations of your GraphQL Schema and more. [Check it out](https://graphql-code-generator.com/)
106 |
107 | The usage is slightly different from the old one:
108 | ```yaml
109 | schema: src/schema/**/*.graphql
110 | extensions:
111 | codegen:
112 | src/generated-types.ts: # Output file name
113 | - typescript # Plugin names to be used
114 | - typescript-resolvers
115 | ```
116 |
117 | For instance, consider a hypothetical case where you need to generate TypeScript resolvers signatures for your GraphQL project. To do this, you would install the `codegen` plugin and the additional plugins and templates for GraphQL Code Generator. For this case, you would need `typescript` and `typescript-resolvers` plugins:
118 |
119 | ```bash
120 | yarn add @graphql-cli/codegen @graphql-codegen/typescript @graphql-codegen/typescript-resolvers --dev
121 | ```
122 |
123 | Now, using a single command, you can run GraphQL Code Generator using GraphQL CLI:
124 | ```bash
125 | graphql codegen
126 | ```
127 |
128 | ### Special Notes for Prisma users
129 | Prisma users will need to download a schema from a URL endpoint. For example, here is a *legacy GraphQL Config file* doing this:
130 |
131 | `.graphqlconfig`
132 | ```yaml
133 | projects:
134 | app:
135 | schemaPath: src/schema.graphql
136 | extensions:
137 | endpoints:
138 | default: http://localhost:4000
139 | database:
140 | schemaPath: src/generated/prisma-client/prisma.graphql
141 | extensions:
142 | prisma: prisma/prisma.yml
143 | ```
144 |
145 | **To update the configuration for the new GraphQL CLI** you need to rename the file to `.graphqlrc.yml`, and then update the file as follows:
146 |
147 | `.graphqlrc.yml`
148 | ```yaml
149 | projects:
150 | app:
151 | schema: src/schema.graphql
152 | database:
153 | schema: prisma/prisma.yml
154 | extensions:
155 | codegen:
156 | generates:
157 | ./src/generated/prisma-client/prisma.graphql:
158 | plugins:
159 | - schema-ast
160 | ```
161 |
162 | You can directly point to your `prisma.yml` file instead of the URL endpoint.
163 |
164 | Before running the GraphQL CLI command to use this new configuration, make sure you have installed the `@graphql-cli/codegen` and `@graphql-codegen/schema-ast` plugins using:
165 | ```sh
166 | yarn add @graphql-cli/codegen @graphql-codegen/schema-ast --dev
167 | ```
168 |
169 | Now you can run `graphql codegen --project database` for generating your `prisma.graphql` file.
170 |
171 | You will also need to update your `prisma.yml` file if you're using `graphql get-schema` with Prisma:
172 | ```yaml
173 | ...
174 | ## Ensures Prisma client is re-generated after a datamodel change.
175 | hooks:
176 | post-deploy:
177 | - graphql codegen --project database # instead of graphql get-schema
178 | - prisma generate
179 | ```
180 |
--------------------------------------------------------------------------------
/templates/fullstack/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/fullstack/server/src/generated-types.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | import { GraphQLResolveInfo } from 'graphql';
3 | import { comment, note } from './generated-db-types';
4 | import { GraphbackRuntimeContext } from '@graphback/runtime';
5 | export type Maybe = T | null;
6 | export type Exact = { [K in keyof T]: T[K] };
7 | export type Omit = Pick>;
8 | export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable };
9 |
10 | /** All built-in and custom scalars, mapped to their actual values */
11 | export type Scalars = {
12 | ID: string;
13 | String: string;
14 | Boolean: boolean;
15 | Int: number;
16 | Float: number;
17 | };
18 |
19 | /** @model */
20 | export type Comment = {
21 | __typename?: 'Comment';
22 | id: Scalars['ID'];
23 | text?: Maybe;
24 | description?: Maybe;
25 | /** @manyToOne(field: 'comments', key: 'noteId') */
26 | note?: Maybe;
27 | };
28 |
29 | export type CommentFilter = {
30 | id?: Maybe;
31 | text?: Maybe;
32 | description?: Maybe;
33 | noteId?: Maybe;
34 | and?: Maybe>;
35 | or?: Maybe>;
36 | not?: Maybe;
37 | };
38 |
39 | export type CommentResultList = {
40 | __typename?: 'CommentResultList';
41 | items: Array>;
42 | offset?: Maybe;
43 | limit?: Maybe;
44 | count?: Maybe;
45 | };
46 |
47 | export type CommentSubscriptionFilter = {
48 | id?: Maybe;
49 | text?: Maybe;
50 | description?: Maybe;
51 | };
52 |
53 | export type CreateCommentInput = {
54 | id?: Maybe;
55 | text?: Maybe;
56 | description?: Maybe;
57 | noteId?: Maybe;
58 | };
59 |
60 | export type CreateNoteInput = {
61 | id?: Maybe;
62 | title: Scalars['String'];
63 | description?: Maybe;
64 | };
65 |
66 | export type IdInput = {
67 | ne?: Maybe;
68 | eq?: Maybe;
69 | le?: Maybe;
70 | lt?: Maybe;
71 | ge?: Maybe;
72 | gt?: Maybe;
73 | in?: Maybe>;
74 | };
75 |
76 | export type MutateCommentInput = {
77 | id: Scalars['ID'];
78 | text?: Maybe;
79 | description?: Maybe;
80 | noteId?: Maybe;
81 | };
82 |
83 | export type MutateNoteInput = {
84 | id: Scalars['ID'];
85 | title?: Maybe;
86 | description?: Maybe;
87 | };
88 |
89 | export type Mutation = {
90 | __typename?: 'Mutation';
91 | createNote?: Maybe;
92 | updateNote?: Maybe;
93 | deleteNote?: Maybe;
94 | createComment?: Maybe;
95 | updateComment?: Maybe;
96 | deleteComment?: Maybe;
97 | };
98 |
99 |
100 | export type MutationCreateNoteArgs = {
101 | input: CreateNoteInput;
102 | };
103 |
104 |
105 | export type MutationUpdateNoteArgs = {
106 | input: MutateNoteInput;
107 | };
108 |
109 |
110 | export type MutationDeleteNoteArgs = {
111 | input: MutateNoteInput;
112 | };
113 |
114 |
115 | export type MutationCreateCommentArgs = {
116 | input: CreateCommentInput;
117 | };
118 |
119 |
120 | export type MutationUpdateCommentArgs = {
121 | input: MutateCommentInput;
122 | };
123 |
124 |
125 | export type MutationDeleteCommentArgs = {
126 | input: MutateCommentInput;
127 | };
128 |
129 | /** @model */
130 | export type Note = {
131 | __typename?: 'Note';
132 | id: Scalars['ID'];
133 | title: Scalars['String'];
134 | description?: Maybe;
135 | /** @oneToMany(field: 'note', key: 'noteId') */
136 | comments: Array>;
137 | };
138 |
139 |
140 | /** @model */
141 | export type NoteCommentsArgs = {
142 | filter?: Maybe;
143 | };
144 |
145 | export type NoteFilter = {
146 | id?: Maybe;
147 | title?: Maybe;
148 | description?: Maybe;
149 | and?: Maybe>;
150 | or?: Maybe>;
151 | not?: Maybe;
152 | };
153 |
154 | export type NoteResultList = {
155 | __typename?: 'NoteResultList';
156 | items: Array>;
157 | offset?: Maybe;
158 | limit?: Maybe;
159 | count?: Maybe;
160 | };
161 |
162 | export type NoteSubscriptionFilter = {
163 | id?: Maybe;
164 | title?: Maybe;
165 | description?: Maybe;
166 | };
167 |
168 | export type OrderByInput = {
169 | field: Scalars['String'];
170 | order?: Maybe;
171 | };
172 |
173 | export type PageRequest = {
174 | limit?: Maybe;
175 | offset?: Maybe;
176 | };
177 |
178 | export type Query = {
179 | __typename?: 'Query';
180 | getNote?: Maybe;
181 | findNotes: NoteResultList;
182 | getComment?: Maybe;
183 | findComments: CommentResultList;
184 | };
185 |
186 |
187 | export type QueryGetNoteArgs = {
188 | id: Scalars['ID'];
189 | };
190 |
191 |
192 | export type QueryFindNotesArgs = {
193 | filter?: Maybe;
194 | page?: Maybe;
195 | orderBy?: Maybe;
196 | };
197 |
198 |
199 | export type QueryGetCommentArgs = {
200 | id: Scalars['ID'];
201 | };
202 |
203 |
204 | export type QueryFindCommentsArgs = {
205 | filter?: Maybe;
206 | page?: Maybe;
207 | orderBy?: Maybe;
208 | };
209 |
210 | export enum SortDirectionEnum {
211 | Desc = 'DESC',
212 | Asc = 'ASC'
213 | }
214 |
215 | export type StringInput = {
216 | ne?: Maybe;
217 | eq?: Maybe;
218 | le?: Maybe;
219 | lt?: Maybe;
220 | ge?: Maybe;
221 | gt?: Maybe;
222 | in?: Maybe>;
223 | contains?: Maybe;
224 | startsWith?: Maybe;
225 | endsWith?: Maybe;
226 | };
227 |
228 | export type Subscription = {
229 | __typename?: 'Subscription';
230 | newNote: Note;
231 | updatedNote: Note;
232 | deletedNote: Note;
233 | newComment: Comment;
234 | updatedComment: Comment;
235 | deletedComment: Comment;
236 | };
237 |
238 |
239 | export type SubscriptionNewNoteArgs = {
240 | filter?: Maybe;
241 | };
242 |
243 |
244 | export type SubscriptionUpdatedNoteArgs = {
245 | filter?: Maybe;
246 | };
247 |
248 |
249 | export type SubscriptionDeletedNoteArgs = {
250 | filter?: Maybe;
251 | };
252 |
253 |
254 | export type SubscriptionNewCommentArgs = {
255 | filter?: Maybe;
256 | };
257 |
258 |
259 | export type SubscriptionUpdatedCommentArgs = {
260 | filter?: Maybe;
261 | };
262 |
263 |
264 | export type SubscriptionDeletedCommentArgs = {
265 | filter?: Maybe;
266 | };
267 |
268 | export type WithIndex = TObject & Record;
269 | export type ResolversObject = WithIndex;
270 |
271 | export type ResolverTypeWrapper = Promise | T;
272 |
273 |
274 | export type LegacyStitchingResolver = {
275 | fragment: string;
276 | resolve: ResolverFn;
277 | };
278 |
279 | export type NewStitchingResolver = {
280 | selectionSet: string;
281 | resolve: ResolverFn;
282 | };
283 | export type StitchingResolver = LegacyStitchingResolver | NewStitchingResolver;
284 | export type Resolver =
285 | | ResolverFn
286 | | StitchingResolver;
287 |
288 | export type ResolverFn = (
289 | parent: TParent,
290 | args: TArgs,
291 | context: TContext,
292 | info: GraphQLResolveInfo
293 | ) => Promise | TResult;
294 |
295 | export type SubscriptionSubscribeFn = (
296 | parent: TParent,
297 | args: TArgs,
298 | context: TContext,
299 | info: GraphQLResolveInfo
300 | ) => AsyncIterator | Promise>;
301 |
302 | export type SubscriptionResolveFn = (
303 | parent: TParent,
304 | args: TArgs,
305 | context: TContext,
306 | info: GraphQLResolveInfo
307 | ) => TResult | Promise;
308 |
309 | export interface SubscriptionSubscriberObject {
310 | subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
311 | resolve?: SubscriptionResolveFn;
312 | }
313 |
314 | export interface SubscriptionResolverObject {
315 | subscribe: SubscriptionSubscribeFn;
316 | resolve: SubscriptionResolveFn;
317 | }
318 |
319 | export type SubscriptionObject =
320 | | SubscriptionSubscriberObject
321 | | SubscriptionResolverObject;
322 |
323 | export type SubscriptionResolver =
324 | | ((...args: any[]) => SubscriptionObject)
325 | | SubscriptionObject;
326 |
327 | export type TypeResolveFn = (
328 | parent: TParent,
329 | context: TContext,
330 | info: GraphQLResolveInfo
331 | ) => Maybe | Promise>;
332 |
333 | export type IsTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise;
334 |
335 | export type NextResolverFn = () => Promise;
336 |
337 | export type DirectiveResolverFn = (
338 | next: NextResolverFn,
339 | parent: TParent,
340 | args: TArgs,
341 | context: TContext,
342 | info: GraphQLResolveInfo
343 | ) => TResult | Promise;
344 |
345 | /** Mapping between all available schema types and the resolvers types */
346 | export type ResolversTypes = ResolversObject<{
347 | Comment: ResolverTypeWrapper;
348 | ID: ResolverTypeWrapper;
349 | String: ResolverTypeWrapper;
350 | CommentFilter: CommentFilter;
351 | CommentResultList: ResolverTypeWrapper & { items: Array> }>;
352 | Int: ResolverTypeWrapper;
353 | CommentSubscriptionFilter: CommentSubscriptionFilter;
354 | CreateCommentInput: CreateCommentInput;
355 | CreateNoteInput: CreateNoteInput;
356 | IDInput: IdInput;
357 | MutateCommentInput: MutateCommentInput;
358 | MutateNoteInput: MutateNoteInput;
359 | Mutation: ResolverTypeWrapper<{}>;
360 | Note: ResolverTypeWrapper;
361 | NoteFilter: NoteFilter;
362 | NoteResultList: ResolverTypeWrapper & { items: Array> }>;
363 | NoteSubscriptionFilter: NoteSubscriptionFilter;
364 | OrderByInput: OrderByInput;
365 | PageRequest: PageRequest;
366 | Query: ResolverTypeWrapper<{}>;
367 | SortDirectionEnum: SortDirectionEnum;
368 | StringInput: StringInput;
369 | Subscription: ResolverTypeWrapper<{}>;
370 | Boolean: ResolverTypeWrapper;
371 | }>;
372 |
373 | /** Mapping between all available schema types and the resolvers parents */
374 | export type ResolversParentTypes = ResolversObject<{
375 | Comment: comment;
376 | ID: Scalars['ID'];
377 | String: Scalars['String'];
378 | CommentFilter: CommentFilter;
379 | CommentResultList: Omit & { items: Array> };
380 | Int: Scalars['Int'];
381 | CommentSubscriptionFilter: CommentSubscriptionFilter;
382 | CreateCommentInput: CreateCommentInput;
383 | CreateNoteInput: CreateNoteInput;
384 | IDInput: IdInput;
385 | MutateCommentInput: MutateCommentInput;
386 | MutateNoteInput: MutateNoteInput;
387 | Mutation: {};
388 | Note: note;
389 | NoteFilter: NoteFilter;
390 | NoteResultList: Omit & { items: Array> };
391 | NoteSubscriptionFilter: NoteSubscriptionFilter;
392 | OrderByInput: OrderByInput;
393 | PageRequest: PageRequest;
394 | Query: {};
395 | StringInput: StringInput;
396 | Subscription: {};
397 | Boolean: Scalars['Boolean'];
398 | }>;
399 |
400 | export type CommentResolvers = ResolversObject<{
401 | id?: Resolver;
402 | text?: Resolver, ParentType, ContextType>;
403 | description?: Resolver, ParentType, ContextType>;
404 | note?: Resolver, ParentType, ContextType>;
405 | __isTypeOf?: IsTypeOfResolverFn;
406 | }>;
407 |
408 | export type CommentResultListResolvers = ResolversObject<{
409 | items?: Resolver>, ParentType, ContextType>;
410 | offset?: Resolver, ParentType, ContextType>;
411 | limit?: Resolver, ParentType, ContextType>;
412 | count?: Resolver, ParentType, ContextType>;
413 | __isTypeOf?: IsTypeOfResolverFn;
414 | }>;
415 |
416 | export type MutationResolvers = ResolversObject<{
417 | createNote?: Resolver, ParentType, ContextType, RequireFields>;
418 | updateNote?: Resolver, ParentType, ContextType, RequireFields>;
419 | deleteNote?: Resolver, ParentType, ContextType, RequireFields>;
420 | createComment?: Resolver, ParentType, ContextType, RequireFields>;
421 | updateComment?: Resolver, ParentType, ContextType, RequireFields>;
422 | deleteComment?: Resolver, ParentType, ContextType, RequireFields>;
423 | }>;
424 |
425 | export type NoteResolvers = ResolversObject<{
426 | id?: Resolver;
427 | title?: Resolver;
428 | description?: Resolver, ParentType, ContextType>;
429 | comments?: Resolver>, ParentType, ContextType, RequireFields>;
430 | __isTypeOf?: IsTypeOfResolverFn;
431 | }>;
432 |
433 | export type NoteResultListResolvers = ResolversObject<{
434 | items?: Resolver>, ParentType, ContextType>;
435 | offset?: Resolver, ParentType, ContextType>;
436 | limit?: Resolver, ParentType, ContextType>;
437 | count?: Resolver, ParentType, ContextType>;
438 | __isTypeOf?: IsTypeOfResolverFn;
439 | }>;
440 |
441 | export type QueryResolvers = ResolversObject<{
442 | getNote?: Resolver, ParentType, ContextType, RequireFields>;
443 | findNotes?: Resolver>;
444 | getComment?: Resolver, ParentType, ContextType, RequireFields>;
445 | findComments?: Resolver>;
446 | }>;
447 |
448 | export type SubscriptionResolvers = ResolversObject<{
449 | newNote?: SubscriptionResolver>;
450 | updatedNote?: SubscriptionResolver>;
451 | deletedNote?: SubscriptionResolver>;
452 | newComment?: SubscriptionResolver>;
453 | updatedComment?: SubscriptionResolver>;
454 | deletedComment?: SubscriptionResolver>;
455 | }>;
456 |
457 | export type Resolvers = ResolversObject<{
458 | Comment?: CommentResolvers;
459 | CommentResultList?: CommentResultListResolvers;
460 | Mutation?: MutationResolvers;
461 | Note?: NoteResolvers;
462 | NoteResultList?: NoteResultListResolvers;
463 | Query?: QueryResolvers;
464 | Subscription?: SubscriptionResolvers;
465 | }>;
466 |
467 |
468 | /**
469 | * @deprecated
470 | * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config.
471 | */
472 | export type IResolvers = Resolvers;
473 |
--------------------------------------------------------------------------------