@graphql-inspector/validate-command 3.1.4 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { GlobalArgs, CommandFactory } from '@graphql-inspector/commands';
2
2
  import { Source as DocumentSource } from '@graphql-tools/utils';
3
3
  import { GraphQLSchema } from 'graphql';
4
4
  export { CommandFactory };
5
- export declare function handler({ schema, documents, strictFragments, maxDepth, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }: {
5
+ export declare function handler({ schema, documents, strictFragments, maxDepth, maxDirectiveCount, maxAliasCount, maxTokenCount, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }: {
6
6
  schema: GraphQLSchema;
7
7
  documents: DocumentSource[];
8
8
  failOnDeprecated: boolean;
@@ -10,6 +10,9 @@ export declare function handler({ schema, documents, strictFragments, maxDepth,
10
10
  apollo: boolean;
11
11
  keepClientFields: boolean;
12
12
  maxDepth?: number;
13
+ maxDirectiveCount?: number;
14
+ maxAliasCount?: number;
15
+ maxTokenCount?: number;
13
16
  filter?: string[];
14
17
  onlyErrors?: boolean;
15
18
  relativePaths?: boolean;
@@ -24,6 +27,9 @@ declare const _default: CommandFactory<{}, {
24
27
  apollo: boolean;
25
28
  keepClientFields: boolean;
26
29
  maxDepth?: number | undefined;
30
+ maxAliasCount?: number | undefined;
31
+ maxDirectiveCount?: number | undefined;
32
+ maxTokenCount?: number | undefined;
27
33
  filter?: string[] | undefined;
28
34
  onlyErrors?: boolean | undefined;
29
35
  relativePaths?: boolean | undefined;
@@ -10,10 +10,13 @@ const path = require('path');
10
10
  const fs = require('fs');
11
11
  const graphql = require('graphql');
12
12
 
13
- function handler({ schema, documents, strictFragments, maxDepth, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }) {
14
- let invalidDocuments = core.validate(schema, documents.map((doc) => new graphql.Source(graphql.print(doc.document), doc.location)), {
13
+ function handler({ schema, documents, strictFragments, maxDepth, maxDirectiveCount, maxAliasCount, maxTokenCount, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }) {
14
+ let invalidDocuments = core.validate(schema, documents.map(doc => new graphql.Source(graphql.print(doc.document), doc.location)), {
15
15
  strictFragments,
16
16
  maxDepth,
17
+ maxAliasCount,
18
+ maxDirectiveCount,
19
+ maxTokenCount,
17
20
  apollo,
18
21
  keepClientFields,
19
22
  });
@@ -59,7 +62,7 @@ function handler({ schema, documents, strictFragments, maxDepth, apollo, keepCli
59
62
  }
60
63
  }
61
64
  function moveDeprecatedToErrors(docs) {
62
- return docs.map((doc) => {
65
+ return docs.map(doc => {
63
66
  var _a, _b;
64
67
  return ({
65
68
  source: doc.source,
@@ -69,7 +72,7 @@ function moveDeprecatedToErrors(docs) {
69
72
  });
70
73
  }
71
74
  function useRelativePaths(docs) {
72
- return docs.map((doc) => {
75
+ return docs.map(doc => {
73
76
  doc.source.name = path.relative(process.cwd(), doc.source.name);
74
77
  return doc;
75
78
  });
@@ -78,9 +81,9 @@ function useFilter(docs, patterns) {
78
81
  if (!patterns || !patterns.length) {
79
82
  return docs;
80
83
  }
81
- return docs.filter((doc) => patterns.some((filepath) => doc.source.name.includes(filepath)));
84
+ return docs.filter(doc => patterns.some(filepath => doc.source.name.includes(filepath)));
82
85
  }
83
- const index = commands.createCommand((api) => {
86
+ const index = commands.createCommand(api => {
84
87
  const { loaders } = api;
85
88
  return {
86
89
  command: 'validate <documents> <schema>',
@@ -113,6 +116,18 @@ const index = commands.createCommand((api) => {
113
116
  describe: 'Fail on deep operations',
114
117
  type: 'number',
115
118
  },
119
+ maxAliasCount: {
120
+ describe: 'Fail on operations with too many aliases',
121
+ type: 'number',
122
+ },
123
+ maxDirectiveCount: {
124
+ describe: 'Fail on operations with too many directives',
125
+ type: 'number',
126
+ },
127
+ maxTokenCount: {
128
+ describe: 'Fail on operations with too many tokens',
129
+ type: 'number',
130
+ },
116
131
  apollo: {
117
132
  describe: 'Support Apollo directives',
118
133
  type: 'boolean',
@@ -162,7 +177,10 @@ const index = commands.createCommand((api) => {
162
177
  const aws = args.aws || false;
163
178
  const apolloFederation = args.federation || false;
164
179
  const method = ((_a = args.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'POST';
165
- const maxDepth = args.maxDepth || undefined;
180
+ const maxDepth = args.maxDepth != null ? args.maxDepth : undefined;
181
+ const maxAliasCount = args.maxAliasCount != null ? args.maxAliasCount : undefined;
182
+ const maxDirectiveCount = args.maxDirectiveCount != null ? args.maxDirectiveCount : undefined;
183
+ const maxTokenCount = args.maxTokenCount != null ? args.maxTokenCount : undefined;
166
184
  const strictFragments = !args.noStrictFragments;
167
185
  const keepClientFields = args.keepClientFields || false;
168
186
  const failOnDeprecated = args.deprecated;
@@ -184,6 +202,9 @@ const index = commands.createCommand((api) => {
184
202
  documents,
185
203
  apollo,
186
204
  maxDepth,
205
+ maxAliasCount,
206
+ maxDirectiveCount,
207
+ maxTokenCount,
187
208
  strictFragments,
188
209
  keepClientFields,
189
210
  failOnDeprecated,
@@ -199,14 +220,13 @@ const index = commands.createCommand((api) => {
199
220
  });
200
221
  function countErrors(invalidDocuments) {
201
222
  if (invalidDocuments.length) {
202
- return invalidDocuments.filter((doc) => doc.errors && doc.errors.length)
203
- .length;
223
+ return invalidDocuments.filter(doc => doc.errors && doc.errors.length).length;
204
224
  }
205
225
  return 0;
206
226
  }
207
227
  function countDeprecated(invalidDocuments) {
208
228
  if (invalidDocuments.length) {
209
- return invalidDocuments.filter((doc) => doc.deprecated && doc.deprecated.length).length;
229
+ return invalidDocuments.filter(doc => doc.deprecated && doc.deprecated.length).length;
210
230
  }
211
231
  return 0;
212
232
  }
@@ -214,18 +234,16 @@ function printInvalidDocuments(invalidDocuments, listKey, isError = false, silen
214
234
  if (silent) {
215
235
  return;
216
236
  }
217
- invalidDocuments.forEach((doc) => {
237
+ invalidDocuments.forEach(doc => {
218
238
  if (doc.errors.length) {
219
- renderErrors(doc.source.name, doc[listKey], isError).forEach((line) => {
239
+ renderErrors(doc.source.name, doc[listKey], isError).forEach(line => {
220
240
  logger.Logger.log(line);
221
241
  });
222
242
  }
223
243
  });
224
244
  }
225
245
  function renderErrors(sourceName, errors, isError = false) {
226
- const errorsAsString = errors
227
- .map((e) => ` - ${logger.bolderize(e.message)}`)
228
- .join('\n');
246
+ const errorsAsString = errors.map(e => ` - ${logger.bolderize(e.message)}`).join('\n');
229
247
  return [
230
248
  isError ? logger.chalk.redBright('error') : logger.chalk.yellowBright('warn'),
231
249
  `in ${sourceName}:\n\n`,
@@ -6,10 +6,13 @@ import { relative } from 'path';
6
6
  import { writeFileSync } from 'fs';
7
7
  import { Source, print } from 'graphql';
8
8
 
9
- function handler({ schema, documents, strictFragments, maxDepth, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }) {
10
- let invalidDocuments = validate(schema, documents.map((doc) => new Source(print(doc.document), doc.location)), {
9
+ function handler({ schema, documents, strictFragments, maxDepth, maxDirectiveCount, maxAliasCount, maxTokenCount, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, }) {
10
+ let invalidDocuments = validate(schema, documents.map(doc => new Source(print(doc.document), doc.location)), {
11
11
  strictFragments,
12
12
  maxDepth,
13
+ maxAliasCount,
14
+ maxDirectiveCount,
15
+ maxTokenCount,
13
16
  apollo,
14
17
  keepClientFields,
15
18
  });
@@ -55,7 +58,7 @@ function handler({ schema, documents, strictFragments, maxDepth, apollo, keepCli
55
58
  }
56
59
  }
57
60
  function moveDeprecatedToErrors(docs) {
58
- return docs.map((doc) => {
61
+ return docs.map(doc => {
59
62
  var _a, _b;
60
63
  return ({
61
64
  source: doc.source,
@@ -65,7 +68,7 @@ function moveDeprecatedToErrors(docs) {
65
68
  });
66
69
  }
67
70
  function useRelativePaths(docs) {
68
- return docs.map((doc) => {
71
+ return docs.map(doc => {
69
72
  doc.source.name = relative(process.cwd(), doc.source.name);
70
73
  return doc;
71
74
  });
@@ -74,9 +77,9 @@ function useFilter(docs, patterns) {
74
77
  if (!patterns || !patterns.length) {
75
78
  return docs;
76
79
  }
77
- return docs.filter((doc) => patterns.some((filepath) => doc.source.name.includes(filepath)));
80
+ return docs.filter(doc => patterns.some(filepath => doc.source.name.includes(filepath)));
78
81
  }
79
- const index = createCommand((api) => {
82
+ const index = createCommand(api => {
80
83
  const { loaders } = api;
81
84
  return {
82
85
  command: 'validate <documents> <schema>',
@@ -109,6 +112,18 @@ const index = createCommand((api) => {
109
112
  describe: 'Fail on deep operations',
110
113
  type: 'number',
111
114
  },
115
+ maxAliasCount: {
116
+ describe: 'Fail on operations with too many aliases',
117
+ type: 'number',
118
+ },
119
+ maxDirectiveCount: {
120
+ describe: 'Fail on operations with too many directives',
121
+ type: 'number',
122
+ },
123
+ maxTokenCount: {
124
+ describe: 'Fail on operations with too many tokens',
125
+ type: 'number',
126
+ },
112
127
  apollo: {
113
128
  describe: 'Support Apollo directives',
114
129
  type: 'boolean',
@@ -158,7 +173,10 @@ const index = createCommand((api) => {
158
173
  const aws = args.aws || false;
159
174
  const apolloFederation = args.federation || false;
160
175
  const method = ((_a = args.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'POST';
161
- const maxDepth = args.maxDepth || undefined;
176
+ const maxDepth = args.maxDepth != null ? args.maxDepth : undefined;
177
+ const maxAliasCount = args.maxAliasCount != null ? args.maxAliasCount : undefined;
178
+ const maxDirectiveCount = args.maxDirectiveCount != null ? args.maxDirectiveCount : undefined;
179
+ const maxTokenCount = args.maxTokenCount != null ? args.maxTokenCount : undefined;
162
180
  const strictFragments = !args.noStrictFragments;
163
181
  const keepClientFields = args.keepClientFields || false;
164
182
  const failOnDeprecated = args.deprecated;
@@ -180,6 +198,9 @@ const index = createCommand((api) => {
180
198
  documents,
181
199
  apollo,
182
200
  maxDepth,
201
+ maxAliasCount,
202
+ maxDirectiveCount,
203
+ maxTokenCount,
183
204
  strictFragments,
184
205
  keepClientFields,
185
206
  failOnDeprecated,
@@ -195,14 +216,13 @@ const index = createCommand((api) => {
195
216
  });
196
217
  function countErrors(invalidDocuments) {
197
218
  if (invalidDocuments.length) {
198
- return invalidDocuments.filter((doc) => doc.errors && doc.errors.length)
199
- .length;
219
+ return invalidDocuments.filter(doc => doc.errors && doc.errors.length).length;
200
220
  }
201
221
  return 0;
202
222
  }
203
223
  function countDeprecated(invalidDocuments) {
204
224
  if (invalidDocuments.length) {
205
- return invalidDocuments.filter((doc) => doc.deprecated && doc.deprecated.length).length;
225
+ return invalidDocuments.filter(doc => doc.deprecated && doc.deprecated.length).length;
206
226
  }
207
227
  return 0;
208
228
  }
@@ -210,18 +230,16 @@ function printInvalidDocuments(invalidDocuments, listKey, isError = false, silen
210
230
  if (silent) {
211
231
  return;
212
232
  }
213
- invalidDocuments.forEach((doc) => {
233
+ invalidDocuments.forEach(doc => {
214
234
  if (doc.errors.length) {
215
- renderErrors(doc.source.name, doc[listKey], isError).forEach((line) => {
235
+ renderErrors(doc.source.name, doc[listKey], isError).forEach(line => {
216
236
  Logger.log(line);
217
237
  });
218
238
  }
219
239
  });
220
240
  }
221
241
  function renderErrors(sourceName, errors, isError = false) {
222
- const errorsAsString = errors
223
- .map((e) => ` - ${bolderize(e.message)}`)
224
- .join('\n');
242
+ const errorsAsString = errors.map(e => ` - ${bolderize(e.message)}`).join('\n');
225
243
  return [
226
244
  isError ? chalk.redBright('error') : chalk.yellowBright('warn'),
227
245
  `in ${sourceName}:\n\n`,
package/package.json CHANGED
@@ -1,51 +1,48 @@
1
1
  {
2
2
  "name": "@graphql-inspector/validate-command",
3
- "version": "3.1.4",
3
+ "version": "3.4.0",
4
4
  "description": "Validate Documents in GraphQL Inspector",
5
+ "sideEffects": false,
6
+ "peerDependencies": {
7
+ "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
8
+ },
9
+ "dependencies": {
10
+ "@graphql-inspector/commands": "3.4.0",
11
+ "@graphql-inspector/core": "3.4.0",
12
+ "@graphql-inspector/logger": "3.4.0",
13
+ "tslib": "^2.0.0"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "kamilkisiela/graphql-inspector",
18
+ "directory": "packages/commands/validate"
19
+ },
5
20
  "keywords": [
6
21
  "graphql",
7
22
  "graphql-inspector",
8
23
  "graphql-inspector-command",
9
24
  "tools"
10
25
  ],
11
- "sideEffects": false,
12
- "main": "dist/index.js",
13
- "module": "dist/index.mjs",
14
- "exports": {
15
- ".": {
16
- "require": "./dist/index.js",
17
- "import": "./dist/index.mjs"
18
- },
19
- "./*": {
20
- "require": "./dist/*.js",
21
- "import": "./dist/*.mjs"
22
- }
23
- },
24
- "typings": "dist/index.d.ts",
25
- "typescript": {
26
- "definition": "dist/index.d.ts"
27
- },
28
26
  "author": {
29
27
  "name": "Kamil Kisiela",
30
28
  "email": "kamil.kisiela@gmail.com",
31
29
  "url": "https://github.com/kamilkisiela"
32
30
  },
33
31
  "license": "MIT",
34
- "repository": {
35
- "type": "git",
36
- "url": "kamilkisiela/graphql-inspector",
37
- "directory": "packages/commands/validate"
38
- },
39
- "peerDependencies": {
40
- "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
41
- },
42
- "dependencies": {
43
- "@graphql-inspector/commands": "3.1.4",
44
- "@graphql-inspector/core": "3.1.4",
45
- "@graphql-inspector/logger": "3.1.4",
46
- "tslib": "^2.0.0"
32
+ "main": "index.js",
33
+ "module": "index.mjs",
34
+ "typings": "index.d.ts",
35
+ "typescript": {
36
+ "definition": "index.d.ts"
47
37
  },
48
- "scripts": {
49
- "prepack": "bob prepack"
38
+ "exports": {
39
+ ".": {
40
+ "require": "./index.js",
41
+ "import": "./index.mjs"
42
+ },
43
+ "./*": {
44
+ "require": "./*.js",
45
+ "import": "./*.mjs"
46
+ }
50
47
  }
51
48
  }
@@ -1,135 +0,0 @@
1
- import '@graphql-inspector/testing';
2
- import yargs from 'yargs';
3
- import { buildSchema, parse } from 'graphql';
4
- import { relative } from 'path';
5
- import { mockCommand } from '@graphql-inspector/commands';
6
- import { mockLogger, unmockLogger } from '@graphql-inspector/logger';
7
- import createCommand from '../src';
8
-
9
- const schema = buildSchema(/* GraphQL */ `
10
- type Post {
11
- id: ID
12
- title: String
13
- createdAt: String
14
- modifiedAt: String
15
- }
16
-
17
- type Query {
18
- post: Post!
19
- }
20
- `);
21
-
22
- const operation = parse(/* GraphQL */ `
23
- query post {
24
- post {
25
- id
26
- title
27
- createdAtSomePoint
28
- }
29
- }
30
- `);
31
-
32
- const validate = createCommand({
33
- config: {
34
- use: {
35
- commands: [],
36
- loaders: [],
37
- },
38
- },
39
- loaders: {
40
- async loadSchema() {
41
- return schema;
42
- },
43
- async loadDocuments() {
44
- return [
45
- {
46
- document: operation,
47
- location: 'document.graphql',
48
- },
49
- {
50
- document: operation,
51
- location: 'document2.graphql',
52
- },
53
- {
54
- document: parse(/* GraphQL */ `
55
- query post {
56
- post {
57
- id
58
- title
59
- }
60
- }
61
- `),
62
- location: 'valid-document.graphql',
63
- },
64
- ];
65
- },
66
- },
67
- });
68
-
69
- describe('validate', () => {
70
- let spyReporter: jest.SpyInstance;
71
- let spyProcessExit: jest.SpyInstance;
72
- let spyProcessCwd: jest.SpyInstance;
73
-
74
- beforeEach(() => {
75
- spyProcessExit = jest.spyOn(process, 'exit');
76
- spyProcessExit.mockImplementation();
77
-
78
- spyProcessCwd = jest
79
- .spyOn(process, 'cwd')
80
- .mockImplementation(() => __dirname);
81
-
82
- spyReporter = jest.fn();
83
- mockLogger(spyReporter as any);
84
- });
85
-
86
- afterEach(() => {
87
- spyProcessExit.mockRestore();
88
- spyProcessCwd.mockRestore();
89
- spyReporter.mockRestore();
90
- unmockLogger();
91
- yargs.reset();
92
- });
93
-
94
- test('should load graphql files', async () => {
95
- await mockCommand(validate, 'validate "*.graphql" schema.graphql');
96
-
97
- expect(spyReporter).toHaveBeenCalledNormalized(
98
- 'Detected 2 invalid documents:',
99
- );
100
- expect(spyReporter).toHaveBeenCalledNormalized('document.graphql:');
101
- expect(spyReporter).toHaveBeenCalledNormalized(
102
- 'Cannot query field createdAtSomePoint on type Post',
103
- );
104
- expect(spyReporter).not.toHaveBeenCalledNormalized(
105
- 'All documents are valid',
106
- );
107
- });
108
-
109
- test('should allow to filter results by file paths', async () => {
110
- await mockCommand(
111
- validate,
112
- 'validate "*.graphql" schema.graphql --filter document2.graphql',
113
- );
114
-
115
- expect(spyReporter).not.toHaveBeenCalledNormalized('document.graphql:');
116
- expect(spyReporter).toHaveBeenCalledNormalized('document2.graphql:');
117
- });
118
-
119
- test('should allow to show relative paths', async () => {
120
- await mockCommand(
121
- validate,
122
- 'validate "*.graphql" schema.graphql --relativePaths',
123
- );
124
-
125
- expect(spyReporter).toHaveBeenCalledNormalized(
126
- `in ${relative(process.cwd(), 'document.graphql')}:`,
127
- );
128
- });
129
-
130
- test('should allow for silent mode', async () => {
131
- await mockCommand(validate, 'validate "*.graphql" schema.graphql --silent');
132
-
133
- expect(spyReporter).not.toHaveBeenCalledNormalized('document.graphql:');
134
- });
135
- });
package/dist/package.json DELETED
@@ -1,48 +0,0 @@
1
- {
2
- "name": "@graphql-inspector/validate-command",
3
- "version": "3.1.4",
4
- "description": "Validate Documents in GraphQL Inspector",
5
- "sideEffects": false,
6
- "peerDependencies": {
7
- "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
8
- },
9
- "dependencies": {
10
- "@graphql-inspector/commands": "3.1.4",
11
- "@graphql-inspector/core": "3.1.4",
12
- "@graphql-inspector/logger": "3.1.4",
13
- "tslib": "^2.0.0"
14
- },
15
- "repository": {
16
- "type": "git",
17
- "url": "kamilkisiela/graphql-inspector",
18
- "directory": "packages/commands/validate"
19
- },
20
- "keywords": [
21
- "graphql",
22
- "graphql-inspector",
23
- "graphql-inspector-command",
24
- "tools"
25
- ],
26
- "author": {
27
- "name": "Kamil Kisiela",
28
- "email": "kamil.kisiela@gmail.com",
29
- "url": "https://github.com/kamilkisiela"
30
- },
31
- "license": "MIT",
32
- "main": "index.js",
33
- "module": "index.mjs",
34
- "typings": "index.d.ts",
35
- "typescript": {
36
- "definition": "index.d.ts"
37
- },
38
- "exports": {
39
- ".": {
40
- "require": "./index.js",
41
- "import": "./index.mjs"
42
- },
43
- "./*": {
44
- "require": "./*.js",
45
- "import": "./*.mjs"
46
- }
47
- }
48
- }
package/src/index.ts DELETED
@@ -1,350 +0,0 @@
1
- import {
2
- createCommand,
3
- GlobalArgs,
4
- parseGlobalArgs,
5
- CommandFactory,
6
- } from '@graphql-inspector/commands';
7
- import { Logger, bolderize, chalk } from '@graphql-inspector/logger';
8
- import {
9
- validate as validateDocuments,
10
- InvalidDocument,
11
- } from '@graphql-inspector/core';
12
- import { Source as DocumentSource } from '@graphql-tools/utils';
13
- import { relative } from 'path';
14
- import { writeFileSync } from 'fs';
15
- import { Source, print, GraphQLSchema, GraphQLError } from 'graphql';
16
-
17
- export { CommandFactory };
18
-
19
- export function handler({
20
- schema,
21
- documents,
22
- strictFragments,
23
- maxDepth,
24
- apollo,
25
- keepClientFields,
26
- failOnDeprecated,
27
- filter,
28
- onlyErrors,
29
- relativePaths,
30
- output,
31
- silent,
32
- }: {
33
- schema: GraphQLSchema;
34
- documents: DocumentSource[];
35
- failOnDeprecated: boolean;
36
- strictFragments: boolean;
37
- apollo: boolean;
38
- keepClientFields: boolean;
39
- maxDepth?: number;
40
- filter?: string[];
41
- onlyErrors?: boolean;
42
- relativePaths?: boolean;
43
- output?: string;
44
- silent?: boolean;
45
- }) {
46
- let invalidDocuments = validateDocuments(
47
- schema,
48
- documents.map((doc) => new Source(print(doc.document!), doc.location)),
49
- {
50
- strictFragments,
51
- maxDepth,
52
- apollo,
53
- keepClientFields,
54
- },
55
- );
56
-
57
- if (!invalidDocuments.length) {
58
- Logger.success('All documents are valid');
59
- } else {
60
- if (failOnDeprecated) {
61
- invalidDocuments = moveDeprecatedToErrors(invalidDocuments);
62
- }
63
-
64
- if (relativePaths) {
65
- invalidDocuments = useRelativePaths(invalidDocuments);
66
- }
67
-
68
- const errorsCount = countErrors(invalidDocuments);
69
- const deprecated = countDeprecated(invalidDocuments);
70
- const shouldFailProcess = errorsCount > 0;
71
-
72
- if (errorsCount) {
73
- if (!silent) {
74
- Logger.log(
75
- `\nDetected ${errorsCount} invalid document${
76
- errorsCount > 1 ? 's' : ''
77
- }:\n`,
78
- );
79
- }
80
-
81
- printInvalidDocuments(
82
- useFilter(invalidDocuments, filter),
83
- 'errors',
84
- true,
85
- silent,
86
- );
87
- } else {
88
- Logger.success('All documents are valid');
89
- }
90
-
91
- if (deprecated && !onlyErrors) {
92
- if (!silent) {
93
- Logger.info(
94
- `\nDetected ${deprecated} document${
95
- deprecated > 1 ? 's' : ''
96
- } with deprecated fields:\n`,
97
- );
98
- }
99
-
100
- printInvalidDocuments(
101
- useFilter(invalidDocuments, filter),
102
- 'deprecated',
103
- false,
104
- silent,
105
- );
106
- }
107
-
108
- if (output) {
109
- writeFileSync(
110
- output,
111
- JSON.stringify(
112
- {
113
- status: !shouldFailProcess,
114
- documents: useFilter(invalidDocuments, filter),
115
- },
116
- null,
117
- 2,
118
- ),
119
- {
120
- encoding: 'utf-8',
121
- },
122
- );
123
- }
124
-
125
- if (shouldFailProcess) {
126
- process.exit(1);
127
- }
128
- }
129
- }
130
-
131
- function moveDeprecatedToErrors(docs: InvalidDocument[]) {
132
- return docs.map((doc) => ({
133
- source: doc.source,
134
- errors: [...(doc.errors ?? []), ...(doc.deprecated ?? [])],
135
- deprecated: [],
136
- }));
137
- }
138
-
139
- function useRelativePaths(docs: InvalidDocument[]) {
140
- return docs.map((doc) => {
141
- doc.source.name = relative(process.cwd(), doc.source.name);
142
- return doc;
143
- });
144
- }
145
-
146
- function useFilter(docs: InvalidDocument[], patterns?: string[]) {
147
- if (!patterns || !patterns.length) {
148
- return docs;
149
- }
150
-
151
- return docs.filter((doc) =>
152
- patterns.some((filepath) => doc.source.name.includes(filepath)),
153
- );
154
- }
155
-
156
- export default createCommand<
157
- {},
158
- {
159
- schema: string;
160
- documents: string;
161
- deprecated: boolean;
162
- noStrictFragments: boolean;
163
- apollo: boolean;
164
- keepClientFields: boolean;
165
- maxDepth?: number;
166
- filter?: string[];
167
- onlyErrors?: boolean;
168
- relativePaths?: boolean;
169
- output?: string;
170
- silent?: boolean;
171
- ignore?: string[];
172
- } & GlobalArgs
173
- >((api) => {
174
- const { loaders } = api;
175
-
176
- return {
177
- command: 'validate <documents> <schema>',
178
- describe: 'Validate Fragments and Operations',
179
- builder(yargs) {
180
- return yargs
181
- .positional('schema', {
182
- describe: 'Point to a schema',
183
- type: 'string',
184
- demandOption: true,
185
- })
186
- .positional('documents', {
187
- describe: 'Point to documents',
188
- type: 'string',
189
- demandOption: true,
190
- })
191
- .options({
192
- deprecated: {
193
- alias: 'd',
194
- describe: 'Fail on deprecated usage',
195
- type: 'boolean',
196
- default: false,
197
- },
198
- noStrictFragments: {
199
- describe: 'Do not fail on duplicated fragment names',
200
- type: 'boolean',
201
- default: false,
202
- },
203
- maxDepth: {
204
- describe: 'Fail on deep operations',
205
- type: 'number',
206
- },
207
- apollo: {
208
- describe: 'Support Apollo directives',
209
- type: 'boolean',
210
- default: false,
211
- },
212
- keepClientFields: {
213
- describe:
214
- 'Keeps the fields with @client, but removes @client directive from them',
215
- type: 'boolean',
216
- default: false,
217
- },
218
- filter: {
219
- describe: 'Show results only from a list of files (or file)',
220
- array: true,
221
- type: 'string',
222
- },
223
- ignore: {
224
- describe: 'Ignore and do not load these files (supports glob)',
225
- array: true,
226
- type: 'string',
227
- },
228
- onlyErrors: {
229
- describe: 'Show only errors',
230
- type: 'boolean',
231
- default: false,
232
- },
233
- relativePaths: {
234
- describe: 'Show relative paths',
235
- type: 'boolean',
236
- default: false,
237
- },
238
- silent: {
239
- describe: 'Do not print results',
240
- type: 'boolean',
241
- default: false,
242
- },
243
- output: {
244
- describe: 'Output JSON file',
245
- type: 'string',
246
- },
247
- });
248
- },
249
- async handler(args) {
250
- const { headers, token } = parseGlobalArgs(args);
251
- const apollo = args.apollo || false;
252
- const aws = args.aws || false;
253
- const apolloFederation = args.federation || false;
254
- const method = args.method?.toUpperCase() || 'POST';
255
- const maxDepth = args.maxDepth || undefined;
256
- const strictFragments = !args.noStrictFragments;
257
- const keepClientFields = args.keepClientFields || false;
258
- const failOnDeprecated = args.deprecated;
259
- const output = args.output;
260
- const silent = args.silent || false;
261
- const relativePaths = args.relativePaths || false;
262
- const onlyErrors = args.onlyErrors || false;
263
- const ignore = args.ignore || [];
264
-
265
- const schema = await loaders.loadSchema(
266
- args.schema,
267
- {
268
- headers,
269
- token,
270
- method,
271
- },
272
- apolloFederation,
273
- aws,
274
- );
275
- const documents = await loaders.loadDocuments(args.documents, {
276
- ignore,
277
- });
278
-
279
- return handler({
280
- schema,
281
- documents,
282
- apollo,
283
- maxDepth,
284
- strictFragments,
285
- keepClientFields,
286
- failOnDeprecated,
287
- filter: args.filter,
288
- silent,
289
- output,
290
- relativePaths,
291
- onlyErrors,
292
- });
293
- },
294
- };
295
- });
296
-
297
- function countErrors(invalidDocuments: InvalidDocument[]): number {
298
- if (invalidDocuments.length) {
299
- return invalidDocuments.filter((doc) => doc.errors && doc.errors.length)
300
- .length;
301
- }
302
-
303
- return 0;
304
- }
305
-
306
- function countDeprecated(invalidDocuments: InvalidDocument[]): number {
307
- if (invalidDocuments.length) {
308
- return invalidDocuments.filter(
309
- (doc) => doc.deprecated && doc.deprecated.length,
310
- ).length;
311
- }
312
-
313
- return 0;
314
- }
315
-
316
- function printInvalidDocuments(
317
- invalidDocuments: InvalidDocument[],
318
- listKey: 'errors' | 'deprecated',
319
- isError = false,
320
- silent = false,
321
- ): void {
322
- if (silent) {
323
- return;
324
- }
325
-
326
- invalidDocuments.forEach((doc) => {
327
- if (doc.errors.length) {
328
- renderErrors(doc.source.name, doc[listKey], isError).forEach((line) => {
329
- Logger.log(line);
330
- });
331
- }
332
- });
333
- }
334
-
335
- function renderErrors(
336
- sourceName: string,
337
- errors: GraphQLError[],
338
- isError = false,
339
- ): string[] {
340
- const errorsAsString = errors
341
- .map((e) => ` - ${bolderize(e.message)}`)
342
- .join('\n');
343
-
344
- return [
345
- isError ? chalk.redBright('error') : chalk.yellowBright('warn'),
346
- `in ${sourceName}:\n\n`,
347
- errorsAsString,
348
- '\n\n',
349
- ];
350
- }