graphql-buddy 0.0.0 → 0.1.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.
Files changed (59) hide show
  1. package/README.md +23 -0
  2. package/dist/api.d.ts +42 -8
  3. package/dist/api.js +9 -5
  4. package/dist/chunk-ZIUWDMA7.js +190 -0
  5. package/dist/chunk-ZIUWDMA7.js.map +1 -0
  6. package/dist/cli.js +21 -6
  7. package/dist/cli.js.map +1 -1
  8. package/package.json +1 -1
  9. package/pnpm-workspace.yaml +3 -0
  10. package/src/api/commands/bundle.test.ts +4 -4
  11. package/src/api/commands/bundle.ts +1 -1
  12. package/src/api/commands/validate.test.ts +125 -24
  13. package/src/api/commands/validate.ts +36 -12
  14. package/src/api/shared/GraphqlFiles.ts +61 -0
  15. package/src/api/shared/index.ts +3 -0
  16. package/src/api/shared/loadDocuments.ts +48 -0
  17. package/src/api/shared/loadSchema.ts +11 -57
  18. package/src/api/shared/validateDocuments.ts +45 -0
  19. package/src/cli/commands/validate.ts +22 -5
  20. package/src/cli/index.ts +2 -0
  21. package/test_fixtures/documents/aliased_import/documents/AllFoo.graphql +7 -0
  22. package/test_fixtures/documents/aliased_import/documents/fragments/FooFields.graphql +4 -0
  23. package/test_fixtures/documents/aliased_import/schema/schema.graphql +20 -0
  24. package/test_fixtures/documents/anonymous_operation/documents/Anonymous.graphql +11 -0
  25. package/test_fixtures/documents/anonymous_operation/schema/schema.graphql +20 -0
  26. package/test_fixtures/documents/broken_import/documents/AllFoo.graphql +7 -0
  27. package/test_fixtures/documents/broken_import/schema/schema.graphql +20 -0
  28. package/test_fixtures/documents/duplicate_names/documents/First.graphql +5 -0
  29. package/test_fixtures/documents/duplicate_names/documents/Second.graphql +5 -0
  30. package/test_fixtures/documents/duplicate_names/schema/schema.graphql +20 -0
  31. package/test_fixtures/documents/fragment_library/documents/FooFields.graphql +4 -0
  32. package/test_fixtures/documents/fragment_library/schema/schema.graphql +20 -0
  33. package/test_fixtures/documents/imported_fragment/documents/AllFoo.graphql +12 -0
  34. package/test_fixtures/documents/imported_fragment/documents/FooFields.graphql +4 -0
  35. package/test_fixtures/documents/imported_fragment/schema/schema.graphql +20 -0
  36. package/test_fixtures/documents/invalid_fragment/documents/FooFields.graphql +4 -0
  37. package/test_fixtures/documents/invalid_fragment/schema/schema.graphql +20 -0
  38. package/test_fixtures/documents/missing_argument/documents/MissingArgument.graphql +5 -0
  39. package/test_fixtures/documents/missing_argument/schema/schema.graphql +20 -0
  40. package/test_fixtures/documents/mixed_sdl/documents/MixedSdl.graphql +9 -0
  41. package/test_fixtures/documents/mixed_sdl/schema/schema.graphql +20 -0
  42. package/test_fixtures/documents/nested_imports/documents/AllFoo.graphql +7 -0
  43. package/test_fixtures/documents/nested_imports/documents/BarFields.graphql +5 -0
  44. package/test_fixtures/documents/nested_imports/documents/FooFields.graphql +8 -0
  45. package/test_fixtures/documents/nested_imports/schema/schema.graphql +20 -0
  46. package/test_fixtures/documents/unknown_field/documents/BadFoo.graphql +6 -0
  47. package/test_fixtures/documents/unknown_field/schema/schema.graphql +20 -0
  48. package/test_fixtures/documents/unused_variable/documents/UnusedVariable.graphql +5 -0
  49. package/test_fixtures/documents/unused_variable/schema/schema.graphql +20 -0
  50. package/dist/chunk-Z7B4AQ2U.js +0 -124
  51. package/dist/chunk-Z7B4AQ2U.js.map +0 -1
  52. /package/test_fixtures/{missing_scalar → schema/missing_scalar}/schema.graphql +0 -0
  53. /package/test_fixtures/{multiple_files → schema/multiple_files}/Bar.graphql +0 -0
  54. /package/test_fixtures/{multiple_files → schema/multiple_files}/Baz.graphql +0 -0
  55. /package/test_fixtures/{multiple_files → schema/multiple_files}/Foo.graphql +0 -0
  56. /package/test_fixtures/{multiple_files → schema/multiple_files}/Mutation.graphql +0 -0
  57. /package/test_fixtures/{multiple_files → schema/multiple_files}/Query.graphql +0 -0
  58. /package/test_fixtures/{single_file → schema/single_file}/schema.graphql +0 -0
  59. /package/test_fixtures/{single_file_with_extra_deps → schema/single_file_with_extra_deps}/schema.graphql +0 -0
@@ -4,32 +4,133 @@ import { describe, expect, it } from 'vitest';
4
4
 
5
5
  import * as validate from './validate';
6
6
 
7
+ const schemaFixture = (name: string) => ({
8
+ files: [`**/*.graphql`],
9
+ base: nodePath.resolve(__dirname, `../../../test_fixtures/schema/${name}`),
10
+ });
11
+
12
+ /**
13
+ * Each documents fixture is self-contained: `<case>/schema` holds the schema
14
+ * the documents are validated against and `<case>/documents` holds the
15
+ * operations and fragments under test.
16
+ */
17
+ const documentsFixture = (
18
+ name: string,
19
+ options: { pathAliases?: Map<string, string> } = {},
20
+ ) => {
21
+ const base = nodePath.resolve(
22
+ __dirname,
23
+ `../../../test_fixtures/documents/${name}`,
24
+ );
25
+ return {
26
+ schema: {
27
+ files: [`**/*.graphql`],
28
+ base: nodePath.join(base, `schema`),
29
+ },
30
+ operations: {
31
+ files: [`*.graphql`],
32
+ base: nodePath.join(base, `documents`),
33
+ pathAliases: options.pathAliases,
34
+ },
35
+ };
36
+ };
37
+
7
38
  describe(`validate`, () => {
8
- it(`creates a validation stamp when validation passes.`, async () => {
9
- await expect(
10
- validate.validate({
11
- schema: {
12
- files: [`*.graphql`],
13
- base: nodePath.resolve(
14
- __dirname,
15
- `../../../test_fixtures/multiple_files`,
16
- ),
17
- },
18
- }),
19
- ).resolves.toStrictEqual(expect.any(String));
39
+ describe(`schema`, () => {
40
+ it(`creates a validation stamp when validation passes.`, async () => {
41
+ await expect(
42
+ validate.validate({ schema: schemaFixture(`multiple_files`) }),
43
+ ).resolves.toStrictEqual(expect.any(String));
44
+ });
45
+
46
+ it(`throws for invalid schema.`, async () => {
47
+ await expect(
48
+ validate.validate({ schema: schemaFixture(`missing_scalar`) }),
49
+ ).rejects.toThrowError(`Unknown type "DateTime"`);
50
+ });
20
51
  });
21
52
 
22
- it(`throws for invalid schema.`, async () => {
23
- await expect(
24
- validate.validate({
25
- schema: {
26
- files: [`*.graphql`],
27
- base: nodePath.resolve(
28
- __dirname,
29
- `../../../test_fixtures/missing_scalar`,
30
- ),
31
- },
32
- }),
33
- ).rejects.toThrowError(`Unknown type "DateTime"`);
53
+ describe(`documents`, () => {
54
+ it(`validates operations against the schema, resolving imported fragments.`, async () => {
55
+ await expect(
56
+ validate.validate(documentsFixture(`imported_fragment`)),
57
+ ).resolves.toStrictEqual(expect.any(String));
58
+ });
59
+
60
+ it(`resolves nested #import chains.`, async () => {
61
+ await expect(
62
+ validate.validate(documentsFixture(`nested_imports`)),
63
+ ).resolves.toStrictEqual(expect.any(String));
64
+ });
65
+
66
+ it(`resolves #import paths through path aliases.`, async () => {
67
+ await expect(
68
+ validate.validate(
69
+ documentsFixture(`aliased_import`, {
70
+ pathAliases: new Map([[`@fragments/*`, `fragments/*`]]),
71
+ }),
72
+ ),
73
+ ).resolves.toStrictEqual(expect.any(String));
74
+ });
75
+
76
+ it(`allows fragment-only files that nothing imports.`, async () => {
77
+ await expect(
78
+ validate.validate(documentsFixture(`fragment_library`)),
79
+ ).resolves.toStrictEqual(expect.any(String));
80
+ });
81
+
82
+ it(`still validates field selections inside fragment-only files.`, async () => {
83
+ await expect(
84
+ validate.validate(documentsFixture(`invalid_fragment`)),
85
+ ).rejects.toThrowError(`Cannot query field "thisFieldDoesNotExist"`);
86
+ });
87
+
88
+ it(`throws for operations that reference unknown fields.`, async () => {
89
+ await expect(
90
+ validate.validate(documentsFixture(`unknown_field`)),
91
+ ).rejects.toThrowError(`Cannot query field "thisFieldDoesNotExist"`);
92
+ });
93
+
94
+ it(`throws for operations files that contain type-system definitions.`, async () => {
95
+ await expect(
96
+ validate.validate(documentsFixture(`mixed_sdl`)),
97
+ ).rejects.toThrowError(`The "Bogus" definition is not executable.`);
98
+ });
99
+
100
+ it(`throws for #import of a file that does not exist.`, async () => {
101
+ await expect(
102
+ validate.validate(documentsFixture(`broken_import`)),
103
+ ).rejects.toThrowError(`Cannot find module './DoesNotExist.graphql'`);
104
+ });
105
+
106
+ it(`throws for unused variables.`, async () => {
107
+ await expect(
108
+ validate.validate(documentsFixture(`unused_variable`)),
109
+ ).rejects.toThrowError(`Variable "$id" is never used`);
110
+ });
111
+
112
+ it(`throws for missing required arguments.`, async () => {
113
+ await expect(
114
+ validate.validate(documentsFixture(`missing_argument`)),
115
+ ).rejects.toThrowError(
116
+ `Field "foo" argument "id" of type "ID!" is required`,
117
+ );
118
+ });
119
+
120
+ it(`throws for anonymous operations that share a document with named ones.`, async () => {
121
+ await expect(
122
+ validate.validate(documentsFixture(`anonymous_operation`)),
123
+ ).rejects.toThrowError(
124
+ `This anonymous operation must be the only defined operation.`,
125
+ );
126
+ });
127
+
128
+ // Documents are validated one file at a time, so an operation name reused
129
+ // across files is not detected. This pins that (documented) behavior.
130
+ it(`allows duplicate operation names across separate files.`, async () => {
131
+ await expect(
132
+ validate.validate(documentsFixture(`duplicate_names`)),
133
+ ).resolves.toStrictEqual(expect.any(String));
134
+ });
34
135
  });
35
136
  });
@@ -3,12 +3,18 @@ import * as stringifyObject from 'stringify-object';
3
3
  import * as shared from '../shared';
4
4
 
5
5
  export type ValidateOptions = {
6
- schema: shared.LoadSchemaOptions;
6
+ schema: shared.GraphqlFiles;
7
+
8
+ // Optional GraphQL operation documents (queries, mutations, subscriptions,
9
+ // and fragments) to validate against the loaded schema. When omitted, only
10
+ // the schema itself is validated.
11
+ operations?: shared.GraphqlFiles | null;
7
12
  };
8
13
 
9
14
  /**
10
15
  * Validates that the provided GraphQL schema contain valid syntax and have no
11
- * unknown symbols.
16
+ * unknown symbols. When operations are provided, additionally validates that
17
+ * every operation is valid against that schema.
12
18
  *
13
19
  * @param options - Validate options.
14
20
  *
@@ -22,28 +28,46 @@ export const validate = async (options: ValidateOptions): Promise<string> => {
22
28
  // accurate data in our validation stamp. For example, normalization expands
23
29
  // globs and resolves absolute paths. These are critical steps to ensure the
24
30
  // validation stamp describes without ambiguity which schema were loaded.
25
- const normalizedSchemaOptions = await shared.normalizeLoadSchemaOptions(
31
+ const normalizedSchemaOptions = await shared.normalizeGraphqlFiles(
26
32
  options.schema,
27
33
  );
28
34
 
29
- await shared.loadSchema(normalizedSchemaOptions);
35
+ const schema = await shared.loadSchema(normalizedSchemaOptions);
36
+
37
+ if (options.operations == null) {
38
+ return createValidationStamp({ schema: normalizedSchemaOptions });
39
+ }
40
+
41
+ const normalizedOperationsOptions = await shared.normalizeGraphqlFiles(
42
+ options.operations,
43
+ );
44
+ shared.validateDocuments({
45
+ schema,
46
+ documents: await shared.loadDocuments(normalizedOperationsOptions),
47
+ });
30
48
 
31
49
  return createValidationStamp({
32
- ...options,
33
50
  schema: normalizedSchemaOptions,
51
+ operations: normalizedOperationsOptions,
34
52
  });
35
53
  };
36
54
 
37
- const createValidationStamp = (
38
- options: Omit<ValidateOptions, `schema`> & {
39
- schema: shared.NormalizedLoadSchemaOptions;
40
- },
41
- ): string =>
55
+ const createValidationStamp = (options: {
56
+ schema: shared.NormalizedGraphqlFiles;
57
+ operations?: shared.NormalizedGraphqlFiles;
58
+ }): string =>
42
59
  stringifyObject.default(
43
- { options, outcome: { valid: true } },
60
+ {
61
+ options: {
62
+ schema: options.schema,
63
+ operations: options.operations,
64
+ },
65
+ outcome: { valid: true },
66
+ },
44
67
  {
45
68
  indent: ` `,
46
69
  filter: (container, property) =>
47
- !(container === options.schema && property === `normalized`),
70
+ !(container === options.schema && property === `normalized`) &&
71
+ !(container === options.operations && property === `normalized`),
48
72
  },
49
73
  );
@@ -0,0 +1,61 @@
1
+ import * as nodePath from 'node:path';
2
+
3
+ import * as glob from 'glob';
4
+
5
+ export type GraphqlFiles = UnsafeGraphqlFiles | NormalizedGraphqlFiles;
6
+
7
+ export type UnsafeGraphqlFiles = {
8
+ normalized?: false | null;
9
+
10
+ // Relative paths will be resolved using `base`.
11
+ //
12
+ // Also accepts glob patterns.
13
+ files: Array<string>;
14
+
15
+ // Directory from which `files` are resolved.
16
+ //
17
+ // Defaults to `process.cwd()`.
18
+ base?: string | null;
19
+
20
+ // See @graphql-tools/import#PathAliases
21
+ //
22
+ // Note that relative paths will be resolved relative to `base` to maintain
23
+ // parity with `files`.
24
+ pathAliases?: Map<string, string> | null;
25
+ };
26
+
27
+ export type NormalizedGraphqlFiles = {
28
+ normalized: true;
29
+
30
+ // Absolute paths.
31
+ files: Array<string>;
32
+
33
+ // See @graphql-tools/import#PathAliases
34
+ pathAliases: Map<string, string>;
35
+ };
36
+
37
+ export const normalizeGraphqlFiles = async (
38
+ graphqlFiles: GraphqlFiles,
39
+ ): Promise<NormalizedGraphqlFiles> => {
40
+ if (graphqlFiles.normalized) {
41
+ return graphqlFiles;
42
+ }
43
+
44
+ const base = graphqlFiles.base ?? process.cwd();
45
+ const files = await glob.glob(
46
+ graphqlFiles.files.map((file) => nodePath.resolve(base, file)),
47
+ );
48
+
49
+ const pathAliases = new Map<string, string>();
50
+ if (graphqlFiles.pathAliases != null) {
51
+ for (const [alias, source] of graphqlFiles.pathAliases.entries()) {
52
+ pathAliases.set(alias, nodePath.resolve(base, source));
53
+ }
54
+ }
55
+
56
+ return {
57
+ normalized: true,
58
+ files,
59
+ pathAliases,
60
+ };
61
+ };
@@ -1 +1,4 @@
1
+ export * from './GraphqlFiles';
2
+ export * from './loadDocuments';
1
3
  export * from './loadSchema';
4
+ export * from './validateDocuments';
@@ -0,0 +1,48 @@
1
+ import * as nodeFs from 'node:fs/promises';
2
+
3
+ import * as graphqlFileLoader from '@graphql-tools/graphql-file-loader';
4
+ import * as graphqlLoad from '@graphql-tools/load';
5
+
6
+ import type * as graphqlUtils from '@graphql-tools/utils';
7
+
8
+ import * as graphqlFilesModule from './GraphqlFiles';
9
+
10
+ /**
11
+ * Loads, validates, and parses graphql documents.
12
+ *
13
+ * @param graphqlFiles - Operation or fragment files to process.
14
+ *
15
+ * @returns The parsed GraphQL documents (or throws an error if parsing fails).
16
+ */
17
+ export const loadDocuments = async (
18
+ graphqlFiles: graphqlFilesModule.GraphqlFiles,
19
+ ): Promise<Array<graphqlUtils.Source>> => {
20
+ const { files, pathAliases } =
21
+ await graphqlFilesModule.normalizeGraphqlFiles(graphqlFiles);
22
+
23
+ // @graphql-tools/graphql-file-loader silently skips input files which are not
24
+ // found. I suspect this is so that it can delegate missing inputs to other
25
+ // loaders. For our purposes, this is undesirable because we want to inform
26
+ // clients if expected inputs are missing. For that reason, we manually
27
+ // validate that the files exist first.
28
+ for (const file of files) {
29
+ try {
30
+ await nodeFs.access(file, nodeFs.constants.F_OK);
31
+ } catch {
32
+ throw new Error(`File does not exist: ${file}`);
33
+ }
34
+ }
35
+
36
+ return await graphqlLoad.loadDocuments(files, {
37
+ loaders: [new graphqlFileLoader.GraphQLFileLoader()],
38
+ // By default @graphql-tools/load strips type definitions out of executable
39
+ // documents, so SDL that lands in an operations file (or a schema file
40
+ // caught by an operations glob) would silently vanish. Keeping every
41
+ // definition lets `ExecutableDefinitionsRule` reject it during validation
42
+ // instead.
43
+ filterKinds: [],
44
+ pathAliases: {
45
+ mappings: Object.fromEntries(pathAliases.entries()),
46
+ },
47
+ });
48
+ };
@@ -1,38 +1,24 @@
1
1
  import * as nodeFs from 'node:fs/promises';
2
- import * as nodePath from 'node:path';
3
2
 
4
3
  import * as graphqlFileLoader from '@graphql-tools/graphql-file-loader';
5
4
  import * as graphqlLoad from '@graphql-tools/load';
6
- import * as glob from 'glob';
7
5
 
8
6
  import type * as graphql from 'graphql';
9
7
 
10
- export type LoadSchemaOptions = {
11
- normalized?: false | null;
12
-
13
- // Relative paths will be resolved using `base`.
14
- //
15
- // Also accepts glob patterns.
16
- files: Array<string>;
17
-
18
- // Directory from which `files` are resolved.
19
- //
20
- // Defaults to `process.cwd()`.
21
- base?: string | null;
22
-
23
- // See @graphql-tools/import#PathAliases
24
- //
25
- // Note that relative paths will be resolved relative to `base` to maintain
26
- // parity with `files`.
27
- pathAliases?: Map<string, string> | null;
28
- };
8
+ import * as graphqlFilesModule from './GraphqlFiles';
29
9
 
10
+ /**
11
+ * Loads, validates, and parses graphql schema.
12
+ *
13
+ * @param graphqlFiles - Schema files to process.
14
+ *
15
+ * @returns The parsed GraphQL schema (or throws an error if parsing fails).
16
+ */
30
17
  export const loadSchema = async (
31
- options: LoadSchemaOptions | NormalizedLoadSchemaOptions,
18
+ graphqlFiles: graphqlFilesModule.GraphqlFiles,
32
19
  ): Promise<graphql.GraphQLSchema> => {
33
- const { files, pathAliases } = options.normalized
34
- ? options
35
- : await normalizeLoadSchemaOptions(options);
20
+ const { files, pathAliases } =
21
+ await graphqlFilesModule.normalizeGraphqlFiles(graphqlFiles);
36
22
 
37
23
  // @graphql-tools/graphql-file-loader silently skips input files which are not
38
24
  // found. I suspect this is so that it can delegate missing inputs to other
@@ -54,35 +40,3 @@ export const loadSchema = async (
54
40
  },
55
41
  });
56
42
  };
57
-
58
- export type NormalizedLoadSchemaOptions = {
59
- normalized: true;
60
-
61
- // Absolute paths.
62
- files: Array<string>;
63
-
64
- // See @graphql-tools/import#PathAliases
65
- pathAliases: Map<string, string>;
66
- };
67
-
68
- export const normalizeLoadSchemaOptions = async (
69
- options: LoadSchemaOptions,
70
- ): Promise<NormalizedLoadSchemaOptions> => {
71
- const base = options.base ?? process.cwd();
72
- const files = await glob.glob(
73
- options.files.map((file) => nodePath.resolve(base, file)),
74
- );
75
-
76
- const pathAliases = new Map<string, string>();
77
- if (options.pathAliases != null) {
78
- for (const [alias, source] of options.pathAliases.entries()) {
79
- pathAliases.set(alias, nodePath.resolve(base, source));
80
- }
81
- }
82
-
83
- return {
84
- normalized: true,
85
- files,
86
- pathAliases,
87
- };
88
- };
@@ -0,0 +1,45 @@
1
+ import * as graphql from 'graphql';
2
+
3
+ import type * as graphqlUtils from '@graphql-tools/utils';
4
+
5
+ export type ValidateDocumentsOptions = {
6
+ schema: graphql.GraphQLSchema;
7
+ documents: Array<graphqlUtils.Source>;
8
+ };
9
+
10
+ /**
11
+ * Validates GraphQL documents against their target schema.
12
+ *
13
+ * @param options - Validation options (schema and documents).
14
+ *
15
+ * @throws GraphQLError instances when validation fails.
16
+ */
17
+ export const validateDocuments = (options: ValidateDocumentsOptions): void => {
18
+ const { schema, documents } = options;
19
+
20
+ for (const source of documents) {
21
+ if (source.document == null) {
22
+ continue;
23
+ }
24
+
25
+ // Fragment-only documents are fragment "libraries" that operations pull in
26
+ // via `#import`. Validated in isolation they would trip
27
+ // `NoUnusedFragmentsRule`, so we drop that single rule for them. Every
28
+ // other rule, most importantly that each referenced field, argument, and
29
+ // type actually exists, are still enforced.
30
+ const definesOperation = source.document.definitions.some(
31
+ (definition) => definition.kind === graphql.Kind.OPERATION_DEFINITION,
32
+ );
33
+
34
+ const errors = graphql.validate(
35
+ schema,
36
+ source.document,
37
+ graphql.specifiedRules.filter(
38
+ (rule) => definesOperation || rule !== graphql.NoUnusedFragmentsRule,
39
+ ),
40
+ );
41
+ if (errors.length > 0) {
42
+ throw errors[0];
43
+ }
44
+ }
45
+ };
@@ -9,7 +9,9 @@ import * as shared from '../shared';
9
9
  export const createValidateCommand = () =>
10
10
  new commander.Command()
11
11
  .name(`validate`)
12
- .description(`Checks that all provided GraphQL files form a valid schema.`)
12
+ .description(
13
+ `Checks that all provided GraphQL files form a valid schema, and optionally that operations are valid against it.`,
14
+ )
13
15
  .argument(`<schema...>`, `Input GraphQL schema files to validate.`)
14
16
  .option(
15
17
  `-b, --base <directory>`,
@@ -19,6 +21,10 @@ export const createValidateCommand = () =>
19
21
  `-a, --alias <pattern...>`,
20
22
  `Path alias options for @graphql-tools/load.`,
21
23
  )
24
+ .option(
25
+ `-o, --operations <operations...>`,
26
+ `GraphQL operation documents to validate against the schema.`,
27
+ )
22
28
  .option(
23
29
  `--stamp <file>`,
24
30
  `Creates a validation stamp to confirm valid GraphQL schema.`,
@@ -35,18 +41,29 @@ const validate = async (options: {
35
41
  schema: Array<string>;
36
42
  base?: string;
37
43
  alias?: Array<string>;
44
+ operations?: Array<string>;
38
45
  stamp?: string;
39
46
  silent: boolean;
40
47
  }): Promise<void> => {
41
- const { schema, base, alias, stamp, silent } = options;
48
+ const { schema, base, alias, operations, stamp, silent } = options;
42
49
 
43
- const validationStamp = await api.validate({
50
+ const pathAliases = shared.parsePathAliases(alias ?? []);
51
+
52
+ const validateOptions: api.ValidateOptions = {
44
53
  schema: {
45
54
  files: schema,
46
55
  base,
47
- pathAliases: shared.parsePathAliases(alias ?? []),
56
+ pathAliases,
48
57
  },
49
- });
58
+ };
59
+ if (operations != null) {
60
+ validateOptions.operations = {
61
+ files: operations,
62
+ base,
63
+ pathAliases,
64
+ };
65
+ }
66
+ const validationStamp = await api.validate(validateOptions);
50
67
 
51
68
  if (stamp != null) {
52
69
  const destination = nodePath.resolve(base ?? process.cwd(), stamp);
package/src/cli/index.ts CHANGED
@@ -7,6 +7,8 @@ import * as commands from './commands';
7
7
 
8
8
  const main = () => {
9
9
  process.setUncaughtExceptionCaptureCallback((error) => {
10
+ process.exitCode = 1;
11
+
10
12
  // When thrown natively, GraphQLError prints like any other error with
11
13
  // minimal context, however, when logged with `.toString()` it prints
12
14
  // helpful debugging information critical for debugging syntax errors (such
@@ -0,0 +1,7 @@
1
+ #import "@fragments/FooFields.graphql"
2
+
3
+ query AllFoo {
4
+ allFoo {
5
+ ...FooFields
6
+ }
7
+ }
@@ -0,0 +1,4 @@
1
+ fragment FooFields on Foo {
2
+ id
3
+ name
4
+ }
@@ -0,0 +1,20 @@
1
+ type Query {
2
+ allFoo: [Foo!]!
3
+ foo(id: ID!): Foo
4
+ }
5
+
6
+ type Foo {
7
+ id: ID!
8
+ name: String
9
+ bar: Bar
10
+ }
11
+
12
+ type Bar {
13
+ baz: Baz
14
+ }
15
+
16
+ type Baz {
17
+ dateCreated: DateTime!
18
+ }
19
+
20
+ scalar DateTime
@@ -0,0 +1,11 @@
1
+ query {
2
+ allFoo {
3
+ id
4
+ }
5
+ }
6
+
7
+ query Named {
8
+ allFoo {
9
+ id
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ type Query {
2
+ allFoo: [Foo!]!
3
+ foo(id: ID!): Foo
4
+ }
5
+
6
+ type Foo {
7
+ id: ID!
8
+ name: String
9
+ bar: Bar
10
+ }
11
+
12
+ type Bar {
13
+ baz: Baz
14
+ }
15
+
16
+ type Baz {
17
+ dateCreated: DateTime!
18
+ }
19
+
20
+ scalar DateTime
@@ -0,0 +1,7 @@
1
+ #import "./DoesNotExist.graphql"
2
+
3
+ query AllFoo {
4
+ allFoo {
5
+ ...FooFields
6
+ }
7
+ }
@@ -0,0 +1,20 @@
1
+ type Query {
2
+ allFoo: [Foo!]!
3
+ foo(id: ID!): Foo
4
+ }
5
+
6
+ type Foo {
7
+ id: ID!
8
+ name: String
9
+ bar: Bar
10
+ }
11
+
12
+ type Bar {
13
+ baz: Baz
14
+ }
15
+
16
+ type Baz {
17
+ dateCreated: DateTime!
18
+ }
19
+
20
+ scalar DateTime
@@ -0,0 +1,5 @@
1
+ query AllFoo {
2
+ allFoo {
3
+ id
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ query AllFoo {
2
+ allFoo {
3
+ name
4
+ }
5
+ }
@@ -0,0 +1,20 @@
1
+ type Query {
2
+ allFoo: [Foo!]!
3
+ foo(id: ID!): Foo
4
+ }
5
+
6
+ type Foo {
7
+ id: ID!
8
+ name: String
9
+ bar: Bar
10
+ }
11
+
12
+ type Bar {
13
+ baz: Baz
14
+ }
15
+
16
+ type Baz {
17
+ dateCreated: DateTime!
18
+ }
19
+
20
+ scalar DateTime