From fbbaa7d79bb120bfdb104d386be225d58a700e63 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 16:54:58 +0200 Subject: [PATCH 01/12] Setup the foundation for a new `apollo-graphql` utility library. As of this commit, this package provides nothing! --- packages/apollo-graphql/.npmignore | 6 ++++++ packages/apollo-graphql/CHANGELOG.md | 4 ++++ packages/apollo-graphql/README.md | 1 + packages/apollo-graphql/jest.config.js | 3 +++ packages/apollo-graphql/package.json | 17 +++++++++++++++++ .../apollo-graphql/src/__tests__/tsconfig.json | 7 +++++++ packages/apollo-graphql/src/index.ts | 0 packages/apollo-graphql/tsconfig.json | 10 ++++++++++ 8 files changed, 48 insertions(+) create mode 100644 packages/apollo-graphql/.npmignore create mode 100644 packages/apollo-graphql/CHANGELOG.md create mode 100644 packages/apollo-graphql/README.md create mode 100644 packages/apollo-graphql/jest.config.js create mode 100644 packages/apollo-graphql/package.json create mode 100644 packages/apollo-graphql/src/__tests__/tsconfig.json create mode 100644 packages/apollo-graphql/src/index.ts create mode 100644 packages/apollo-graphql/tsconfig.json diff --git a/packages/apollo-graphql/.npmignore b/packages/apollo-graphql/.npmignore new file mode 100644 index 0000000000..a165046d35 --- /dev/null +++ b/packages/apollo-graphql/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-graphql/CHANGELOG.md b/packages/apollo-graphql/CHANGELOG.md new file mode 100644 index 0000000000..ef45b84f48 --- /dev/null +++ b/packages/apollo-graphql/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +### vNEXT + diff --git a/packages/apollo-graphql/README.md b/packages/apollo-graphql/README.md new file mode 100644 index 0000000000..07909aab7e --- /dev/null +++ b/packages/apollo-graphql/README.md @@ -0,0 +1 @@ +# `apollo-graphql` diff --git a/packages/apollo-graphql/jest.config.js b/packages/apollo-graphql/jest.config.js new file mode 100644 index 0000000000..a383fbc925 --- /dev/null +++ b/packages/apollo-graphql/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../jest.config.base'); + +module.exports = Object.assign(Object.create(null), config); diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json new file mode 100644 index 0000000000..955794a4b0 --- /dev/null +++ b/packages/apollo-graphql/package.json @@ -0,0 +1,17 @@ +{ + "name": "apollo-graphql", + "version": "0.0.0", + "description": "Apollo GraphQL utility library", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "keywords": [], + "author": "Apollo ", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "devDependencies": {}, + "peerDependencies": { + "graphql": "^14.0.0" + } +} diff --git a/packages/apollo-graphql/src/__tests__/tsconfig.json b/packages/apollo-graphql/src/__tests__/tsconfig.json new file mode 100644 index 0000000000..428259da81 --- /dev/null +++ b/packages/apollo-graphql/src/__tests__/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.test.base", + "include": ["**/*"], + "references": [ + { "path": "../../" } + ] +} diff --git a/packages/apollo-graphql/src/index.ts b/packages/apollo-graphql/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/apollo-graphql/tsconfig.json b/packages/apollo-graphql/tsconfig.json new file mode 100644 index 0000000000..4fcb63fe14 --- /dev/null +++ b/packages/apollo-graphql/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["**/__tests__", "**/__mocks__"], + "references": [] +} From e9209a531312d558bbc942f976ef90107d03e718 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 18:22:58 +0200 Subject: [PATCH 02/12] Publish - apollo-graphql@0.0.1-alpha.0 --- packages/apollo-graphql/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index 955794a4b0..ed7ab1471e 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -1,6 +1,6 @@ { "name": "apollo-graphql", - "version": "0.0.0", + "version": "0.0.1-alpha.0", "description": "Apollo GraphQL utility library", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -10,7 +10,6 @@ "engines": { "node": ">=6" }, - "devDependencies": {}, "peerDependencies": { "graphql": "^14.0.0" } From ece011da5b79abf7b36d5328212cd2b44d6ba412 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 17:16:43 +0200 Subject: [PATCH 03/12] Move Apollo Engine signatures into `apollo-graphql`. --- packages/apollo-graphql/package.json | 1 + .../src/__tests__/signature.test.ts | 218 ++++++++++++++++ packages/apollo-graphql/src/signature.ts | 242 ++++++++++++++++++ 3 files changed, 461 insertions(+) create mode 100644 packages/apollo-graphql/src/__tests__/signature.test.ts create mode 100644 packages/apollo-graphql/src/signature.ts diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index ed7ab1471e..517ea16353 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -10,6 +10,7 @@ "engines": { "node": ">=6" }, + "devDependencies": {}, "peerDependencies": { "graphql": "^14.0.0" } diff --git a/packages/apollo-graphql/src/__tests__/signature.test.ts b/packages/apollo-graphql/src/__tests__/signature.test.ts new file mode 100644 index 0000000000..7e3e731448 --- /dev/null +++ b/packages/apollo-graphql/src/__tests__/signature.test.ts @@ -0,0 +1,218 @@ +import { DocumentNode } from 'graphql'; +import { default as gql, disableFragmentWarnings } from 'graphql-tag'; + +import { + printWithReducedWhitespace, + hideLiterals, + dropUnusedDefinitions, + sortAST, + removeAliases, +} from '../signature'; + +// The gql duplicate fragment warning feature really is just warnings; nothing +// breaks if you turn it off in tests. +disableFragmentWarnings(); + +describe('printWithReducedWhitespace', () => { + const cases = [ + { + name: 'lots of whitespace', + // Note: there's a tab after "tab->", which prettier wants to keep as a + // literal tab rather than \t. In the output, there should be a literal + // backslash-t. + input: gql` + query Foo($a: Int) { + user( + name: " tab-> yay" + other: """ + apple + bag + cat + """ + ) { + name + } + } + `, + output: + 'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}', + }, + ]; + cases.forEach(({ name, input, output }) => { + test(name, () => { + expect(printWithReducedWhitespace(input)).toEqual(output); + }); + }); +}); + +describe('hideLiterals', () => { + const cases = [ + { + name: 'full test', + input: gql` + query Foo($b: Int, $a: Boolean) { + user(name: "hello", age: 5) { + ...Bar + ... on User { + hello + bee + } + tz + aliased: name + } + } + + fragment Bar on User { + age @skip(if: $a) + ...Nested + } + + fragment Nested on User { + blah + } + `, + output: + 'query Foo($b:Int,$a:Boolean){user(name:"",age:0){...Bar...on User{hello bee}tz aliased:name}}' + + 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', + }, + ]; + cases.forEach(({ name, input, output }) => { + test(name, () => { + expect(printWithReducedWhitespace(hideLiterals(input))).toEqual(output); + }); + }); +}); + +describe('aggressive signature', () => { + function aggressive(ast: DocumentNode, operationName: string): string { + return printWithReducedWhitespace( + removeAliases( + hideLiterals(sortAST(dropUnusedDefinitions(ast, operationName))), + ), + ); + } + + const cases = [ + // Test cases borrowed from optics-agent-js. + { + name: 'basic test', + operationName: '', + input: gql` + { + user { + name + } + } + `, + output: '{user{name}}', + }, + { + name: 'basic test with query', + operationName: '', + input: gql` + query { + user { + name + } + } + `, + output: '{user{name}}', + }, + { + name: 'basic with operation name', + operationName: 'OpName', + input: gql` + query OpName { + user { + name + } + } + `, + output: 'query OpName{user{name}}', + }, + { + name: 'with various inline types', + operationName: 'OpName', + input: gql` + query OpName { + user { + name(apple: [[10]], cat: ENUM_VALUE, bag: { input: "value" }) + } + } + `, + output: 'query OpName{user{name(apple:[],bag:{},cat:ENUM_VALUE)}}', + }, + { + name: 'with various argument types', + operationName: 'OpName', + input: gql` + query OpName($c: Int!, $a: [[Boolean!]!], $b: EnumType) { + user { + name(apple: $a, cat: $c, bag: $b) + } + } + `, + output: + 'query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}', + }, + { + name: 'fragment', + operationName: '', + input: gql` + { + user { + name + ...Bar + } + } + + fragment Bar on User { + asd + } + + fragment Baz on User { + jkl + } + `, + output: '{user{name...Bar}}fragment Bar on User{asd}', + }, + { + name: 'full test', + operationName: 'Foo', + input: gql` + query Foo($b: Int, $a: Boolean) { + user(name: "hello", age: 5) { + ...Bar + ... on User { + hello + bee + } + tz + aliased: name + } + } + + fragment Baz on User { + asd + } + + fragment Bar on User { + age @skip(if: $a) + ...Nested + } + + fragment Nested on User { + blah + } + `, + output: + 'query Foo($a:Boolean,$b:Int){user(age:0,name:""){name tz...Bar...on User{bee hello}}}' + + 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', + }, + ]; + cases.forEach(({ name, operationName, input, output }) => { + test(name, () => { + expect(aggressive(input, operationName)).toEqual(output); + }); + }); +}); diff --git a/packages/apollo-graphql/src/signature.ts b/packages/apollo-graphql/src/signature.ts new file mode 100644 index 0000000000..64ca5389d1 --- /dev/null +++ b/packages/apollo-graphql/src/signature.ts @@ -0,0 +1,242 @@ +// XXX maybe this should just be its own graphql-signature package + +// In Engine, we want to group requests making the same query together, and +// treat different queries distinctly. But what does it mean for two queries to +// be "the same"? And what if you don't want to send the full text of the query +// to Apollo Engine's servers, either because it contains sensitive data or +// because it contains extraneous operations or fragments? +// +// To solve these problems, EngineReportingAgent has the concept of +// "signatures". We don't (by default) send the full query string of queries to +// the Engine servers. Instead, each trace has its query string's "signature". +// +// You can specify any function mapping a GraphQL query AST (DocumentNode) to +// string as your signature algorithm by providing it as the 'signature' option +// to the EngineReportingAgent constructor. Ideally, your signature should be a +// valid GraphQL query, though as of now the Engine servers do not re-parse your +// signature and do not expect it to match the execution tree in the trace. +// +// This file provides several useful building blocks for writing your own +// signature function. These are: +// +// - dropUnusedDefinitions, which removes operations and fragments that +// aren't going to be used in execution +// - hideLiterals, which replaces all numeric and string literals as well +// as list and object input values with "empty" values +// - removeAliases, which removes field aliasing from the query +// - sortAST, which sorts the children of most multi-child nodes +// consistently +// - printWithReducedWhitespace, a variant on graphql-js's 'print' +// which gets rid of unneeded whitespace +// +// defaultSignature consists of applying all of these building blocks. +// +// Historical note: the default signature algorithm of the Go engineproxy +// performed all of the above operations, and the Engine servers then re-ran a +// mostly identical signature implementation on received traces. This was +// primarily to deal with edge cases where some users used literal interpolation +// instead of GraphQL variables, included randomized alias names, etc. In +// addition, the servers relied on the fact that dropUnusedDefinitions had been +// called in order (and that the signature could be parsed as GraphQL) to +// extract the name of the operation for display. This caused confusion, as the +// query document shown in the Engine UI wasn't the same as the one actually +// sent. apollo-engine-reporting uses a new reporting API which requires it to +// explicitly include the operation name with each signature; this means that +// the server no longer needs to parse the signature or run its own signature +// algorithm on it, and the details of the signature algorithm are now up to the +// reporting agent. + +import { sortBy, ListIteratee } from 'lodash'; + +import { + print, + visit, + DocumentNode, + OperationDefinitionNode, + SelectionSetNode, + FieldNode, + FragmentSpreadNode, + InlineFragmentNode, + FragmentDefinitionNode, + DirectiveNode, + IntValueNode, + FloatValueNode, + StringValueNode, + ListValueNode, + ObjectValueNode, + separateOperations, +} from 'graphql'; + +// Replace numeric, string, list, and object literals with "empty" +// values. Leaves enums alone (since there's no consistent "zero" enum). This +// can help combine similar queries if you substitute values directly into +// queries rather than use GraphQL variables, and can hide sensitive data in +// your query (say, a hardcoded API key) from Engine servers, but in general +// avoiding those situations is better than working around them. +export function hideLiterals(ast: DocumentNode): DocumentNode { + return visit(ast, { + IntValue(node: IntValueNode): IntValueNode { + return { ...node, value: '0' }; + }, + FloatValue(node: FloatValueNode): FloatValueNode { + return { ...node, value: '0' }; + }, + StringValue(node: StringValueNode): StringValueNode { + return { ...node, value: '', block: false }; + }, + ListValue(node: ListValueNode): ListValueNode { + return { ...node, values: [] }; + }, + ObjectValue(node: ObjectValueNode): ObjectValueNode { + return { ...node, fields: [] }; + }, + }); +} + +// A GraphQL query may contain multiple named operations, with the operation to +// use specified separately by the client. This transformation drops unused +// operations from the query, as well as any fragment definitions that are not +// referenced. (In general we recommend that unused definitions are dropped on +// the client before sending to the server to save bandwidth and parsing time.) +export function dropUnusedDefinitions( + ast: DocumentNode, + operationName: string, +): DocumentNode { + const separated = separateOperations(ast)[operationName]; + if (!separated) { + // If the given operationName isn't found, just make this whole transform a + // no-op instead of crashing. + return ast; + } + return separated; +} + +// Like lodash's sortBy, but sorted(undefined) === undefined rather than []. It +// is a stable non-in-place sort. +function sorted( + items: ReadonlyArray | undefined, + ...iteratees: Array> +): Array | undefined { + if (items) { + return sortBy(items, ...iteratees); + } + return undefined; +} + +// sortAST sorts most multi-child nodes alphabetically. Using this as part of +// your signature calculation function may make it easier to tell the difference +// between queries that are similar to each other, and if for some reason your +// GraphQL client generates query strings with elements in nondeterministic +// order, it can make sure the queries are treated as identical. +export function sortAST(ast: DocumentNode): DocumentNode { + return visit(ast, { + OperationDefinition( + node: OperationDefinitionNode, + ): OperationDefinitionNode { + return { + ...node, + variableDefinitions: sorted( + node.variableDefinitions, + 'variable.name.value', + ), + }; + }, + SelectionSet(node: SelectionSetNode): SelectionSetNode { + return { + ...node, + // Define an ordering for field names in a SelectionSet. Field first, + // then FragmentSpread, then InlineFragment. By a lovely coincidence, + // the order we want them to appear in is alphabetical by node.kind. + // Use sortBy instead of sorted because 'selections' is not optional. + selections: sortBy(node.selections, 'kind', 'name.value'), + }; + }, + Field(node: FieldNode): FieldNode { + return { + ...node, + arguments: sorted(node.arguments, 'name.value'), + }; + }, + FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode { + return { ...node, directives: sorted(node.directives, 'name.value') }; + }, + InlineFragment(node: InlineFragmentNode): InlineFragmentNode { + return { ...node, directives: sorted(node.directives, 'name.value') }; + }, + FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode { + return { + ...node, + directives: sorted(node.directives, 'name.value'), + variableDefinitions: sorted( + node.variableDefinitions, + 'variable.name.value', + ), + }; + }, + Directive(node: DirectiveNode): DirectiveNode { + return { ...node, arguments: sorted(node.arguments, 'name.value') }; + }, + }); +} + +// removeAliases gets rid of GraphQL aliases, a feature by which you can tell a +// server to return a field's data under a different name from the field +// name. Maybe this is useful if somebody somewhere inserts random aliases into +// their queries. +export function removeAliases(ast: DocumentNode): DocumentNode { + return visit(ast, { + Field(node: FieldNode): FieldNode { + return { + ...node, + alias: undefined, + }; + }, + }); +} + +// Like the graphql-js print function, but deleting whitespace wherever +// feasible. Specifically, all whitespace (outside of string literals) is +// reduced to at most one space, and even that space is removed anywhere except +// for between two alphanumerics. +export function printWithReducedWhitespace(ast: DocumentNode): string { + // In a GraphQL AST (which notably does not contain comments), the only place + // where meaningful whitespace (or double quotes) can exist is in + // StringNodes. So to print with reduced whitespace, we: + // - temporarily sanitize strings by replacing their contents with hex + // - use the default GraphQL printer + // - minimize the whitespace with a simple regexp replacement + // - convert strings back to their actual value + // We normalize all strings to non-block strings for simplicity. + + const sanitizedAST = visit(ast, { + StringValue(node: StringValueNode): StringValueNode { + return { + ...node, + value: Buffer.from(node.value, 'utf8').toString('hex'), + block: false, + }; + }, + }); + const withWhitespace = print(sanitizedAST); + const minimizedButStillHex = withWhitespace + .replace(/\s+/g, ' ') + .replace(/([^_a-zA-Z0-9]) /g, (_, c) => c) + .replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c); + return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) => + JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')), + ); +} + +// The default signature function consists of removing unused definitions +// and whitespace. +// XXX consider caching somehow +export function defaultSignature( + ast: DocumentNode, + operationName: string, +): string { + return printWithReducedWhitespace( + sortAST( + removeAliases(hideLiterals(dropUnusedDefinitions(ast, operationName))), + ), + ); +} From 806f85714d21f9bd3c8894469677b6777ece7338 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 18:00:06 +0200 Subject: [PATCH 04/12] Move `apollo-engine-reporting` signature calculations to new `apollo-graphql`. Many of these signature calculation functions are now utilized in tools or helpers which are not directly related to `apollo-server` functionality, including various aspects of the `apollo` CLI which live within `apollo-tooling`. Currently, because of `apollo`'s dependency on `apollo-engine-reporting` for this signature, this requires bringing in the entire dependency tree which `apollo-server-core` relies on since `apollo-engine-reporting` depends on `apollo-server-core`. By moving this into this new `apollo-graphql` utility library, we're able to trim that rather hefty dependency tree and drastically reduce the download for running, say, `npx apollo`. --- packages/apollo-graphql/package.json | 3 +++ packages/apollo-graphql/src/index.ts | 1 + packages/apollo-graphql/src/signature.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index 517ea16353..a703b27328 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -10,6 +10,9 @@ "engines": { "node": ">=6" }, + "dependencies": { + "lodash": "^4.17.10" + }, "devDependencies": {}, "peerDependencies": { "graphql": "^14.0.0" diff --git a/packages/apollo-graphql/src/index.ts b/packages/apollo-graphql/src/index.ts index e69de29bb2..bfff26d0b0 100644 --- a/packages/apollo-graphql/src/index.ts +++ b/packages/apollo-graphql/src/index.ts @@ -0,0 +1 @@ +export { defaultEngineReportingSignature } from './signature'; \ No newline at end of file diff --git a/packages/apollo-graphql/src/signature.ts b/packages/apollo-graphql/src/signature.ts index 64ca5389d1..6fa6f6d28c 100644 --- a/packages/apollo-graphql/src/signature.ts +++ b/packages/apollo-graphql/src/signature.ts @@ -230,7 +230,7 @@ export function printWithReducedWhitespace(ast: DocumentNode): string { // The default signature function consists of removing unused definitions // and whitespace. // XXX consider caching somehow -export function defaultSignature( +export function defaultEngineReportingSignature( ast: DocumentNode, operationName: string, ): string { From 63038a4e76c9764ea5ee6068816e51dc4b5d6b9e Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 18:14:30 +0200 Subject: [PATCH 05/12] Move Engine signature AST traversals/transforms into `./transforms` module. These AST visitors and transformations are more generally usable for other purposes rather than just the Apollo Engine signature reporting and would seem to belong in a module of their own. --- .../src/__tests__/signature.test.ts | 72 +------ .../src/__tests__/transforms.test.ts | 77 +++++++ packages/apollo-graphql/src/index.ts | 2 +- packages/apollo-graphql/src/signature.ts | 194 +---------------- packages/apollo-graphql/src/transforms.ts | 195 ++++++++++++++++++ 5 files changed, 284 insertions(+), 256 deletions(-) create mode 100644 packages/apollo-graphql/src/__tests__/transforms.test.ts create mode 100644 packages/apollo-graphql/src/transforms.ts diff --git a/packages/apollo-graphql/src/__tests__/signature.test.ts b/packages/apollo-graphql/src/__tests__/signature.test.ts index 7e3e731448..161e07ef30 100644 --- a/packages/apollo-graphql/src/__tests__/signature.test.ts +++ b/packages/apollo-graphql/src/__tests__/signature.test.ts @@ -7,82 +7,12 @@ import { dropUnusedDefinitions, sortAST, removeAliases, -} from '../signature'; +} from '../transforms'; // The gql duplicate fragment warning feature really is just warnings; nothing // breaks if you turn it off in tests. disableFragmentWarnings(); -describe('printWithReducedWhitespace', () => { - const cases = [ - { - name: 'lots of whitespace', - // Note: there's a tab after "tab->", which prettier wants to keep as a - // literal tab rather than \t. In the output, there should be a literal - // backslash-t. - input: gql` - query Foo($a: Int) { - user( - name: " tab-> yay" - other: """ - apple - bag - cat - """ - ) { - name - } - } - `, - output: - 'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}', - }, - ]; - cases.forEach(({ name, input, output }) => { - test(name, () => { - expect(printWithReducedWhitespace(input)).toEqual(output); - }); - }); -}); - -describe('hideLiterals', () => { - const cases = [ - { - name: 'full test', - input: gql` - query Foo($b: Int, $a: Boolean) { - user(name: "hello", age: 5) { - ...Bar - ... on User { - hello - bee - } - tz - aliased: name - } - } - - fragment Bar on User { - age @skip(if: $a) - ...Nested - } - - fragment Nested on User { - blah - } - `, - output: - 'query Foo($b:Int,$a:Boolean){user(name:"",age:0){...Bar...on User{hello bee}tz aliased:name}}' + - 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', - }, - ]; - cases.forEach(({ name, input, output }) => { - test(name, () => { - expect(printWithReducedWhitespace(hideLiterals(input))).toEqual(output); - }); - }); -}); - describe('aggressive signature', () => { function aggressive(ast: DocumentNode, operationName: string): string { return printWithReducedWhitespace( diff --git a/packages/apollo-graphql/src/__tests__/transforms.test.ts b/packages/apollo-graphql/src/__tests__/transforms.test.ts new file mode 100644 index 0000000000..e0ad5e471d --- /dev/null +++ b/packages/apollo-graphql/src/__tests__/transforms.test.ts @@ -0,0 +1,77 @@ +import { default as gql, disableFragmentWarnings } from 'graphql-tag'; + +import { printWithReducedWhitespace, hideLiterals } from '../transforms'; + +// The gql duplicate fragment warning feature really is just warnings; nothing +// breaks if you turn it off in tests. +disableFragmentWarnings(); + +describe('printWithReducedWhitespace', () => { + const cases = [ + { + name: 'lots of whitespace', + // Note: there's a tab after "tab->", which prettier wants to keep as a + // literal tab rather than \t. In the output, there should be a literal + // backslash-t. + input: gql` + query Foo($a: Int) { + user( + name: " tab-> yay" + other: """ + apple + bag + cat + """ + ) { + name + } + } + `, + output: + 'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}', + }, + ]; + cases.forEach(({ name, input, output }) => { + test(name, () => { + expect(printWithReducedWhitespace(input)).toEqual(output); + }); + }); +}); + +describe('hideLiterals', () => { + const cases = [ + { + name: 'full test', + input: gql` + query Foo($b: Int, $a: Boolean) { + user(name: "hello", age: 5) { + ...Bar + ... on User { + hello + bee + } + tz + aliased: name + } + } + + fragment Bar on User { + age @skip(if: $a) + ...Nested + } + + fragment Nested on User { + blah + } + `, + output: + 'query Foo($b:Int,$a:Boolean){user(name:"",age:0){...Bar...on User{hello bee}tz aliased:name}}' + + 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', + }, + ]; + cases.forEach(({ name, input, output }) => { + test(name, () => { + expect(printWithReducedWhitespace(hideLiterals(input))).toEqual(output); + }); + }); +}); diff --git a/packages/apollo-graphql/src/index.ts b/packages/apollo-graphql/src/index.ts index bfff26d0b0..8161d6647f 100644 --- a/packages/apollo-graphql/src/index.ts +++ b/packages/apollo-graphql/src/index.ts @@ -1 +1 @@ -export { defaultEngineReportingSignature } from './signature'; \ No newline at end of file +export { defaultEngineReportingSignature } from './signature'; diff --git a/packages/apollo-graphql/src/signature.ts b/packages/apollo-graphql/src/signature.ts index 6fa6f6d28c..8ddc71fe9f 100644 --- a/packages/apollo-graphql/src/signature.ts +++ b/packages/apollo-graphql/src/signature.ts @@ -1,5 +1,3 @@ -// XXX maybe this should just be its own graphql-signature package - // In Engine, we want to group requests making the same query together, and // treat different queries distinctly. But what does it mean for two queries to // be "the same"? And what if you don't want to send the full text of the query @@ -16,9 +14,9 @@ // valid GraphQL query, though as of now the Engine servers do not re-parse your // signature and do not expect it to match the execution tree in the trace. // -// This file provides several useful building blocks for writing your own -// signature function. These are: -// +// This module utilizes several AST transformations from the adjacent +// 'transforms' module (which are also for writing your own signature method). + // - dropUnusedDefinitions, which removes operations and fragments that // aren't going to be used in execution // - hideLiterals, which replaces all numeric and string literals as well @@ -46,186 +44,14 @@ // algorithm on it, and the details of the signature algorithm are now up to the // reporting agent. -import { sortBy, ListIteratee } from 'lodash'; - +import { DocumentNode } from 'graphql'; import { - print, - visit, - DocumentNode, - OperationDefinitionNode, - SelectionSetNode, - FieldNode, - FragmentSpreadNode, - InlineFragmentNode, - FragmentDefinitionNode, - DirectiveNode, - IntValueNode, - FloatValueNode, - StringValueNode, - ListValueNode, - ObjectValueNode, - separateOperations, -} from 'graphql'; - -// Replace numeric, string, list, and object literals with "empty" -// values. Leaves enums alone (since there's no consistent "zero" enum). This -// can help combine similar queries if you substitute values directly into -// queries rather than use GraphQL variables, and can hide sensitive data in -// your query (say, a hardcoded API key) from Engine servers, but in general -// avoiding those situations is better than working around them. -export function hideLiterals(ast: DocumentNode): DocumentNode { - return visit(ast, { - IntValue(node: IntValueNode): IntValueNode { - return { ...node, value: '0' }; - }, - FloatValue(node: FloatValueNode): FloatValueNode { - return { ...node, value: '0' }; - }, - StringValue(node: StringValueNode): StringValueNode { - return { ...node, value: '', block: false }; - }, - ListValue(node: ListValueNode): ListValueNode { - return { ...node, values: [] }; - }, - ObjectValue(node: ObjectValueNode): ObjectValueNode { - return { ...node, fields: [] }; - }, - }); -} - -// A GraphQL query may contain multiple named operations, with the operation to -// use specified separately by the client. This transformation drops unused -// operations from the query, as well as any fragment definitions that are not -// referenced. (In general we recommend that unused definitions are dropped on -// the client before sending to the server to save bandwidth and parsing time.) -export function dropUnusedDefinitions( - ast: DocumentNode, - operationName: string, -): DocumentNode { - const separated = separateOperations(ast)[operationName]; - if (!separated) { - // If the given operationName isn't found, just make this whole transform a - // no-op instead of crashing. - return ast; - } - return separated; -} - -// Like lodash's sortBy, but sorted(undefined) === undefined rather than []. It -// is a stable non-in-place sort. -function sorted( - items: ReadonlyArray | undefined, - ...iteratees: Array> -): Array | undefined { - if (items) { - return sortBy(items, ...iteratees); - } - return undefined; -} - -// sortAST sorts most multi-child nodes alphabetically. Using this as part of -// your signature calculation function may make it easier to tell the difference -// between queries that are similar to each other, and if for some reason your -// GraphQL client generates query strings with elements in nondeterministic -// order, it can make sure the queries are treated as identical. -export function sortAST(ast: DocumentNode): DocumentNode { - return visit(ast, { - OperationDefinition( - node: OperationDefinitionNode, - ): OperationDefinitionNode { - return { - ...node, - variableDefinitions: sorted( - node.variableDefinitions, - 'variable.name.value', - ), - }; - }, - SelectionSet(node: SelectionSetNode): SelectionSetNode { - return { - ...node, - // Define an ordering for field names in a SelectionSet. Field first, - // then FragmentSpread, then InlineFragment. By a lovely coincidence, - // the order we want them to appear in is alphabetical by node.kind. - // Use sortBy instead of sorted because 'selections' is not optional. - selections: sortBy(node.selections, 'kind', 'name.value'), - }; - }, - Field(node: FieldNode): FieldNode { - return { - ...node, - arguments: sorted(node.arguments, 'name.value'), - }; - }, - FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode { - return { ...node, directives: sorted(node.directives, 'name.value') }; - }, - InlineFragment(node: InlineFragmentNode): InlineFragmentNode { - return { ...node, directives: sorted(node.directives, 'name.value') }; - }, - FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode { - return { - ...node, - directives: sorted(node.directives, 'name.value'), - variableDefinitions: sorted( - node.variableDefinitions, - 'variable.name.value', - ), - }; - }, - Directive(node: DirectiveNode): DirectiveNode { - return { ...node, arguments: sorted(node.arguments, 'name.value') }; - }, - }); -} - -// removeAliases gets rid of GraphQL aliases, a feature by which you can tell a -// server to return a field's data under a different name from the field -// name. Maybe this is useful if somebody somewhere inserts random aliases into -// their queries. -export function removeAliases(ast: DocumentNode): DocumentNode { - return visit(ast, { - Field(node: FieldNode): FieldNode { - return { - ...node, - alias: undefined, - }; - }, - }); -} - -// Like the graphql-js print function, but deleting whitespace wherever -// feasible. Specifically, all whitespace (outside of string literals) is -// reduced to at most one space, and even that space is removed anywhere except -// for between two alphanumerics. -export function printWithReducedWhitespace(ast: DocumentNode): string { - // In a GraphQL AST (which notably does not contain comments), the only place - // where meaningful whitespace (or double quotes) can exist is in - // StringNodes. So to print with reduced whitespace, we: - // - temporarily sanitize strings by replacing their contents with hex - // - use the default GraphQL printer - // - minimize the whitespace with a simple regexp replacement - // - convert strings back to their actual value - // We normalize all strings to non-block strings for simplicity. - - const sanitizedAST = visit(ast, { - StringValue(node: StringValueNode): StringValueNode { - return { - ...node, - value: Buffer.from(node.value, 'utf8').toString('hex'), - block: false, - }; - }, - }); - const withWhitespace = print(sanitizedAST); - const minimizedButStillHex = withWhitespace - .replace(/\s+/g, ' ') - .replace(/([^_a-zA-Z0-9]) /g, (_, c) => c) - .replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c); - return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) => - JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')), - ); -} + printWithReducedWhitespace, + dropUnusedDefinitions, + removeAliases, + sortAST, + hideLiterals, +} from './transforms'; // The default signature function consists of removing unused definitions // and whitespace. diff --git a/packages/apollo-graphql/src/transforms.ts b/packages/apollo-graphql/src/transforms.ts new file mode 100644 index 0000000000..77759db090 --- /dev/null +++ b/packages/apollo-graphql/src/transforms.ts @@ -0,0 +1,195 @@ +import { visit } from 'graphql/language/visitor'; +import { + DocumentNode, + FloatValueNode, + IntValueNode, + StringValueNode, + OperationDefinitionNode, + SelectionSetNode, + FragmentSpreadNode, + InlineFragmentNode, + DirectiveNode, + FieldNode, + FragmentDefinitionNode, + ObjectValueNode, + ListValueNode, +} from 'graphql/language/ast'; +import { print } from 'graphql/language/printer'; +import { separateOperations } from 'graphql/utilities'; +import { sortBy, ListIteratee } from 'lodash'; + +// Replace numeric, string, list, and object literals with "empty" +// values. Leaves enums alone (since there's no consistent "zero" enum). This +// can help combine similar queries if you substitute values directly into +// queries rather than use GraphQL variables, and can hide sensitive data in +// your query (say, a hardcoded API key) from Engine servers, but in general +// avoiding those situations is better than working around them. +export function hideLiterals(ast: DocumentNode): DocumentNode { + return visit(ast, { + IntValue(node: IntValueNode): IntValueNode { + return { ...node, value: '0' }; + }, + FloatValue(node: FloatValueNode): FloatValueNode { + return { ...node, value: '0' }; + }, + StringValue(node: StringValueNode): StringValueNode { + return { ...node, value: '', block: false }; + }, + ListValue(node: ListValueNode): ListValueNode { + return { ...node, values: [] }; + }, + ObjectValue(node: ObjectValueNode): ObjectValueNode { + return { ...node, fields: [] }; + }, + }); +} + +// In the same spirit as the similarly named `hideLiterals` function, only +// hide string and numeric literals. +export function hideStringAndNumericLiterals(ast: DocumentNode): DocumentNode { + return visit(ast, { + IntValue(node: IntValueNode): IntValueNode { + return { ...node, value: '0' }; + }, + FloatValue(node: FloatValueNode): FloatValueNode { + return { ...node, value: '0' }; + }, + StringValue(node: StringValueNode): StringValueNode { + return { ...node, value: '', block: false }; + }, + }); +} + +// A GraphQL query may contain multiple named operations, with the operation to +// use specified separately by the client. This transformation drops unused +// operations from the query, as well as any fragment definitions that are not +// referenced. (In general we recommend that unused definitions are dropped on +// the client before sending to the server to save bandwidth and parsing time.) +export function dropUnusedDefinitions( + ast: DocumentNode, + operationName: string, +): DocumentNode { + const separated = separateOperations(ast)[operationName]; + if (!separated) { + // If the given operationName isn't found, just make this whole transform a + // no-op instead of crashing. + return ast; + } + return separated; +} + +// Like lodash's sortBy, but sorted(undefined) === undefined rather than []. It +// is a stable non-in-place sort. +function sorted( + items: ReadonlyArray | undefined, + ...iteratees: Array> +): Array | undefined { + if (items) { + return sortBy(items, ...iteratees); + } + return undefined; +} + +// sortAST sorts most multi-child nodes alphabetically. Using this as part of +// your signature calculation function may make it easier to tell the difference +// between queries that are similar to each other, and if for some reason your +// GraphQL client generates query strings with elements in nondeterministic +// order, it can make sure the queries are treated as identical. +export function sortAST(ast: DocumentNode): DocumentNode { + return visit(ast, { + OperationDefinition( + node: OperationDefinitionNode, + ): OperationDefinitionNode { + return { + ...node, + variableDefinitions: sorted( + node.variableDefinitions, + 'variable.name.value', + ), + }; + }, + SelectionSet(node: SelectionSetNode): SelectionSetNode { + return { + ...node, + // Define an ordering for field names in a SelectionSet. Field first, + // then FragmentSpread, then InlineFragment. By a lovely coincidence, + // the order we want them to appear in is alphabetical by node.kind. + // Use sortBy instead of sorted because 'selections' is not optional. + selections: sortBy(node.selections, 'kind', 'name.value'), + }; + }, + Field(node: FieldNode): FieldNode { + return { + ...node, + arguments: sorted(node.arguments, 'name.value'), + }; + }, + FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode { + return { ...node, directives: sorted(node.directives, 'name.value') }; + }, + InlineFragment(node: InlineFragmentNode): InlineFragmentNode { + return { ...node, directives: sorted(node.directives, 'name.value') }; + }, + FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode { + return { + ...node, + directives: sorted(node.directives, 'name.value'), + variableDefinitions: sorted( + node.variableDefinitions, + 'variable.name.value', + ), + }; + }, + Directive(node: DirectiveNode): DirectiveNode { + return { ...node, arguments: sorted(node.arguments, 'name.value') }; + }, + }); +} + +// removeAliases gets rid of GraphQL aliases, a feature by which you can tell a +// server to return a field's data under a different name from the field +// name. Maybe this is useful if somebody somewhere inserts random aliases into +// their queries. +export function removeAliases(ast: DocumentNode): DocumentNode { + return visit(ast, { + Field(node: FieldNode): FieldNode { + return { + ...node, + alias: undefined, + }; + }, + }); +} + +// Like the graphql-js print function, but deleting whitespace wherever +// feasible. Specifically, all whitespace (outside of string literals) is +// reduced to at most one space, and even that space is removed anywhere except +// for between two alphanumerics. +export function printWithReducedWhitespace(ast: DocumentNode): string { + // In a GraphQL AST (which notably does not contain comments), the only place + // where meaningful whitespace (or double quotes) can exist is in + // StringNodes. So to print with reduced whitespace, we: + // - temporarily sanitize strings by replacing their contents with hex + // - use the default GraphQL printer + // - minimize the whitespace with a simple regexp replacement + // - convert strings back to their actual value + // We normalize all strings to non-block strings for simplicity. + + const sanitizedAST = visit(ast, { + StringValue(node: StringValueNode): StringValueNode { + return { + ...node, + value: Buffer.from(node.value, 'utf8').toString('hex'), + block: false, + }; + }, + }); + const withWhitespace = print(sanitizedAST); + const minimizedButStillHex = withWhitespace + .replace(/\s+/g, ' ') + .replace(/([^_a-zA-Z0-9]) /g, (_, c) => c) + .replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c); + return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) => + JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')), + ); +} From 48706bda42503854c4114bad612c2a24a42ab2d4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 1 Feb 2019 19:16:48 +0200 Subject: [PATCH 06/12] Use `lodash.sortby` modularly, rather than all of `lodash`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the only place that we use `lodash` in the entire `apollo-server` repository is to utilize the `sortBy` function in this signature generation. Looking at the bundle stats, it appears that lodash represents 7.1% of the `apollo-server` package. We're a server, so bundle size is generally less of a concern, but it's still not to be ignored, particularly as we move into worker environments. More pressingly though, since this package will be utilized by the `apollo` CLI, we'll be shaving precious download time off the invocation of `npx apollo` if we can get this down. By switching to the modular package (but still depending on `@types/lodash` for _just_ the `ListIteratee` type — which we only need in development — we should be able to trim 55.4kB minified (19.1kB minified+gzip'd) off the `apollo-server` build. cc @trevor-scheer @jbaxleyiii @martijnwalraven --- packages/apollo-graphql/package.json | 2 +- packages/apollo-graphql/src/transforms.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index a703b27328..8c70e35a2c 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -11,7 +11,7 @@ "node": ">=6" }, "dependencies": { - "lodash": "^4.17.10" + "lodash.sortby": "^4.7.0" }, "devDependencies": {}, "peerDependencies": { diff --git a/packages/apollo-graphql/src/transforms.ts b/packages/apollo-graphql/src/transforms.ts index 77759db090..c8565f5c37 100644 --- a/packages/apollo-graphql/src/transforms.ts +++ b/packages/apollo-graphql/src/transforms.ts @@ -16,7 +16,11 @@ import { } from 'graphql/language/ast'; import { print } from 'graphql/language/printer'; import { separateOperations } from 'graphql/utilities'; -import { sortBy, ListIteratee } from 'lodash'; +// We'll only fetch the `ListIteratee` type from the `@types/lodash`, but get +// `sortBy` from the modularized version of the package to avoid bringing in +// all of `lodash`. +import { ListIteratee } from 'lodash'; +import sortBy from 'lodash.sortby'; // Replace numeric, string, list, and object literals with "empty" // values. Leaves enums alone (since there's no consistent "zero" enum). This From 064646be2a779311ac914753e21d1cf914e744e5 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 7 Feb 2019 13:01:14 +0200 Subject: [PATCH 07/12] Publish - apollo-cache-control@0.5.0-alpha.3 - apollo-datasource-rest@0.3.0-alpha.3 - apollo-engine-reporting@1.0.0-alpha.3 - apollo-graphql@0.0.1-alpha.1 - apollo-server-azure-functions@2.4.0-alpha.4 - apollo-server-cloud-functions@2.4.0-alpha.4 - apollo-server-cloudflare@2.4.0-alpha.4 - apollo-server-core@2.4.0-alpha.4 - apollo-server-express@2.4.0-alpha.4 - apollo-server-hapi@2.4.0-alpha.4 - apollo-server-integration-testsuite@2.4.0-alpha.4 - apollo-server-koa@2.4.0-alpha.4 - apollo-server-lambda@2.4.0-alpha.4 - apollo-server-micro@2.4.0-alpha.4 - apollo-server-plugin-base@0.3.0-alpha.4 - apollo-server-testing@2.4.0-alpha.4 - apollo-server@2.4.0-alpha.4 - apollo-tracing@0.5.0-alpha.3 - graphql-extensions@0.5.0-alpha.4 --- packages/apollo-graphql/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index 8c70e35a2c..98495c2ff5 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -1,6 +1,6 @@ { "name": "apollo-graphql", - "version": "0.0.1-alpha.0", + "version": "0.0.1-alpha.1", "description": "Apollo GraphQL utility library", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -13,7 +13,6 @@ "dependencies": { "lodash.sortby": "^4.7.0" }, - "devDependencies": {}, "peerDependencies": { "graphql": "^14.0.0" } From a07e658400427e8363ceea25694ab37d665e08a6 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 7 Feb 2019 14:08:23 +0200 Subject: [PATCH 08/12] Publish - apollo-cache-control@0.5.0 - apollo-datasource-rest@0.3.0 - apollo-datasource@0.3.0 - apollo-engine-reporting@1.0.0 - apollo-graphql@0.1.0 - apollo-server-azure-functions@2.4.0 - apollo-server-cache-memcached@0.3.0 - apollo-server-cache-redis@0.3.0 - apollo-server-caching@0.3.0 - apollo-server-cloud-functions@2.4.0 - apollo-server-cloudflare@2.4.0 - apollo-server-core@2.4.0 - apollo-server-express@2.4.0 - apollo-server-hapi@2.4.0 - apollo-server-integration-testsuite@2.4.0 - apollo-server-koa@2.4.0 - apollo-server-lambda@2.4.0 - apollo-server-micro@2.4.0 - apollo-server-plugin-base@0.3.0 - apollo-server-testing@2.4.0 - apollo-server@2.4.0 - apollo-tracing@0.5.0 - graphql-extensions@0.5.0 --- packages/apollo-graphql/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index 98495c2ff5..5b16427571 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -1,6 +1,6 @@ { "name": "apollo-graphql", - "version": "0.0.1-alpha.1", + "version": "0.1.0", "description": "Apollo GraphQL utility library", "main": "dist/index.js", "types": "dist/index.d.ts", From 3decc837beef15e1b64fb61b2ccb914db044b067 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Tue, 12 Feb 2019 16:48:55 -0800 Subject: [PATCH 09/12] Move apollo-graphql package into the apollo-tooling repo --- CHANGELOG.md | 2 + package-lock.json | 47 ++++++++++--- package.json | 1 + packages/apollo-graphql/.npmignore | 4 +- packages/apollo-graphql/jest.config.js | 3 - packages/apollo-graphql/package.json | 6 +- .../src/__tests__/signature.test.ts | 58 +++++++-------- .../src/__tests__/transforms.test.ts | 20 +++--- packages/apollo-graphql/src/index.ts | 2 +- packages/apollo-graphql/src/signature.ts | 12 ++-- packages/apollo-graphql/src/transforms.ts | 70 +++++++++---------- packages/apollo-graphql/tsconfig.json | 2 +- packages/apollo-graphql/tsconfig.test.json | 4 ++ tsconfig.json | 3 +- 14 files changed, 132 insertions(+), 102 deletions(-) delete mode 100644 packages/apollo-graphql/jest.config.js create mode 100644 packages/apollo-graphql/tsconfig.test.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0634f2f412..c0690961a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - `apollo-codegen-typescript` - Add `/* eslint-disable */` in generated files header [#1017](https://github.com/apollographql/apollo-tooling/pull/1017) +- `apollo-graphql` + - Move apollo-graphql package from apollo-server to apollo-tooling ## `apollo@2.5.0`, `apollo-language-server@1.5.0`, `vscode-apollo@1.5.0` diff --git a/package-lock.json b/package-lock.json index 4fe80c23d3..db41150244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2179,6 +2179,12 @@ "node-fetch": "^2.2.0" } }, + "apollo-graphql": { + "version": "file:packages/apollo-graphql", + "requires": { + "lodash": "^4.7.0" + } + }, "apollo-language-server": { "version": "file:packages/apollo-language-server", "requires": { @@ -5940,7 +5946,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5961,12 +5968,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5981,17 +5990,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6108,7 +6120,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6120,6 +6133,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6134,6 +6148,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6141,12 +6156,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6165,6 +6182,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6245,7 +6263,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6257,6 +6276,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6342,7 +6362,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6378,6 +6399,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6397,6 +6419,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6440,12 +6463,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/package.json b/package.json index b2a24ca126..21eec444b0 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "apollo-codegen-swift": "file:packages/apollo-codegen-swift", "apollo-codegen-typescript": "file:packages/apollo-codegen-typescript", "apollo-env": "file:packages/apollo-env", + "apollo-graphql": "file:packages/apollo-graphql", "apollo-language-server": "file:packages/apollo-language-server", "vscode-apollo": "file:packages/vscode-apollo" }, diff --git a/packages/apollo-graphql/.npmignore b/packages/apollo-graphql/.npmignore index a165046d35..4c04b261db 100644 --- a/packages/apollo-graphql/.npmignore +++ b/packages/apollo-graphql/.npmignore @@ -1,6 +1,6 @@ * !src/**/* -!dist/**/* -dist/**/*.test.* +!lib/**/* +lib/**/*.test.* !package.json !README.md diff --git a/packages/apollo-graphql/jest.config.js b/packages/apollo-graphql/jest.config.js deleted file mode 100644 index a383fbc925..0000000000 --- a/packages/apollo-graphql/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const config = require('../../jest.config.base'); - -module.exports = Object.assign(Object.create(null), config); diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index 5b16427571..e9391fa94b 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -2,8 +2,8 @@ "name": "apollo-graphql", "version": "0.1.0", "description": "Apollo GraphQL utility library", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "lib/index.js", + "types": "lib/index.d.ts", "keywords": [], "author": "Apollo ", "license": "MIT", @@ -14,6 +14,6 @@ "lodash.sortby": "^4.7.0" }, "peerDependencies": { - "graphql": "^14.0.0" + "graphql": "^14.0.2" } } diff --git a/packages/apollo-graphql/src/__tests__/signature.test.ts b/packages/apollo-graphql/src/__tests__/signature.test.ts index 161e07ef30..e0077831b9 100644 --- a/packages/apollo-graphql/src/__tests__/signature.test.ts +++ b/packages/apollo-graphql/src/__tests__/signature.test.ts @@ -1,32 +1,32 @@ -import { DocumentNode } from 'graphql'; -import { default as gql, disableFragmentWarnings } from 'graphql-tag'; +import { DocumentNode } from "graphql"; +import { default as gql, disableFragmentWarnings } from "graphql-tag"; import { printWithReducedWhitespace, hideLiterals, dropUnusedDefinitions, sortAST, - removeAliases, -} from '../transforms'; + removeAliases +} from "../transforms"; // The gql duplicate fragment warning feature really is just warnings; nothing // breaks if you turn it off in tests. disableFragmentWarnings(); -describe('aggressive signature', () => { +describe("aggressive signature", () => { function aggressive(ast: DocumentNode, operationName: string): string { return printWithReducedWhitespace( removeAliases( - hideLiterals(sortAST(dropUnusedDefinitions(ast, operationName))), - ), + hideLiterals(sortAST(dropUnusedDefinitions(ast, operationName))) + ) ); } const cases = [ // Test cases borrowed from optics-agent-js. { - name: 'basic test', - operationName: '', + name: "basic test", + operationName: "", input: gql` { user { @@ -34,11 +34,11 @@ describe('aggressive signature', () => { } } `, - output: '{user{name}}', + output: "{user{name}}" }, { - name: 'basic test with query', - operationName: '', + name: "basic test with query", + operationName: "", input: gql` query { user { @@ -46,11 +46,11 @@ describe('aggressive signature', () => { } } `, - output: '{user{name}}', + output: "{user{name}}" }, { - name: 'basic with operation name', - operationName: 'OpName', + name: "basic with operation name", + operationName: "OpName", input: gql` query OpName { user { @@ -58,11 +58,11 @@ describe('aggressive signature', () => { } } `, - output: 'query OpName{user{name}}', + output: "query OpName{user{name}}" }, { - name: 'with various inline types', - operationName: 'OpName', + name: "with various inline types", + operationName: "OpName", input: gql` query OpName { user { @@ -70,11 +70,11 @@ describe('aggressive signature', () => { } } `, - output: 'query OpName{user{name(apple:[],bag:{},cat:ENUM_VALUE)}}', + output: "query OpName{user{name(apple:[],bag:{},cat:ENUM_VALUE)}}" }, { - name: 'with various argument types', - operationName: 'OpName', + name: "with various argument types", + operationName: "OpName", input: gql` query OpName($c: Int!, $a: [[Boolean!]!], $b: EnumType) { user { @@ -83,11 +83,11 @@ describe('aggressive signature', () => { } `, output: - 'query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}', + "query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!){user{name(apple:$a,bag:$b,cat:$c)}}" }, { - name: 'fragment', - operationName: '', + name: "fragment", + operationName: "", input: gql` { user { @@ -104,11 +104,11 @@ describe('aggressive signature', () => { jkl } `, - output: '{user{name...Bar}}fragment Bar on User{asd}', + output: "{user{name...Bar}}fragment Bar on User{asd}" }, { - name: 'full test', - operationName: 'Foo', + name: "full test", + operationName: "Foo", input: gql` query Foo($b: Int, $a: Boolean) { user(name: "hello", age: 5) { @@ -137,8 +137,8 @@ describe('aggressive signature', () => { `, output: 'query Foo($a:Boolean,$b:Int){user(age:0,name:""){name tz...Bar...on User{bee hello}}}' + - 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', - }, + "fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}" + } ]; cases.forEach(({ name, operationName, input, output }) => { test(name, () => { diff --git a/packages/apollo-graphql/src/__tests__/transforms.test.ts b/packages/apollo-graphql/src/__tests__/transforms.test.ts index e0ad5e471d..c57b5ba67e 100644 --- a/packages/apollo-graphql/src/__tests__/transforms.test.ts +++ b/packages/apollo-graphql/src/__tests__/transforms.test.ts @@ -1,15 +1,15 @@ -import { default as gql, disableFragmentWarnings } from 'graphql-tag'; +import { default as gql, disableFragmentWarnings } from "graphql-tag"; -import { printWithReducedWhitespace, hideLiterals } from '../transforms'; +import { printWithReducedWhitespace, hideLiterals } from "../transforms"; // The gql duplicate fragment warning feature really is just warnings; nothing // breaks if you turn it off in tests. disableFragmentWarnings(); -describe('printWithReducedWhitespace', () => { +describe("printWithReducedWhitespace", () => { const cases = [ { - name: 'lots of whitespace', + name: "lots of whitespace", // Note: there's a tab after "tab->", which prettier wants to keep as a // literal tab rather than \t. In the output, there should be a literal // backslash-t. @@ -28,8 +28,8 @@ describe('printWithReducedWhitespace', () => { } `, output: - 'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}', - }, + 'query Foo($a:Int){user(name:" tab->\\tyay",other:"apple\\n bag\\ncat"){name}}' + } ]; cases.forEach(({ name, input, output }) => { test(name, () => { @@ -38,10 +38,10 @@ describe('printWithReducedWhitespace', () => { }); }); -describe('hideLiterals', () => { +describe("hideLiterals", () => { const cases = [ { - name: 'full test', + name: "full test", input: gql` query Foo($b: Int, $a: Boolean) { user(name: "hello", age: 5) { @@ -66,8 +66,8 @@ describe('hideLiterals', () => { `, output: 'query Foo($b:Int,$a:Boolean){user(name:"",age:0){...Bar...on User{hello bee}tz aliased:name}}' + - 'fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}', - }, + "fragment Bar on User{age@skip(if:$a)...Nested}fragment Nested on User{blah}" + } ]; cases.forEach(({ name, input, output }) => { test(name, () => { diff --git a/packages/apollo-graphql/src/index.ts b/packages/apollo-graphql/src/index.ts index 8161d6647f..407dab5687 100644 --- a/packages/apollo-graphql/src/index.ts +++ b/packages/apollo-graphql/src/index.ts @@ -1 +1 @@ -export { defaultEngineReportingSignature } from './signature'; +export { defaultEngineReportingSignature } from "./signature"; diff --git a/packages/apollo-graphql/src/signature.ts b/packages/apollo-graphql/src/signature.ts index 8ddc71fe9f..6f8b227c8b 100644 --- a/packages/apollo-graphql/src/signature.ts +++ b/packages/apollo-graphql/src/signature.ts @@ -44,25 +44,25 @@ // algorithm on it, and the details of the signature algorithm are now up to the // reporting agent. -import { DocumentNode } from 'graphql'; +import { DocumentNode } from "graphql"; import { printWithReducedWhitespace, dropUnusedDefinitions, removeAliases, sortAST, - hideLiterals, -} from './transforms'; + hideLiterals +} from "./transforms"; // The default signature function consists of removing unused definitions // and whitespace. // XXX consider caching somehow export function defaultEngineReportingSignature( ast: DocumentNode, - operationName: string, + operationName: string ): string { return printWithReducedWhitespace( sortAST( - removeAliases(hideLiterals(dropUnusedDefinitions(ast, operationName))), - ), + removeAliases(hideLiterals(dropUnusedDefinitions(ast, operationName))) + ) ); } diff --git a/packages/apollo-graphql/src/transforms.ts b/packages/apollo-graphql/src/transforms.ts index c8565f5c37..3124d21888 100644 --- a/packages/apollo-graphql/src/transforms.ts +++ b/packages/apollo-graphql/src/transforms.ts @@ -1,4 +1,4 @@ -import { visit } from 'graphql/language/visitor'; +import { visit } from "graphql/language/visitor"; import { DocumentNode, FloatValueNode, @@ -12,15 +12,15 @@ import { FieldNode, FragmentDefinitionNode, ObjectValueNode, - ListValueNode, -} from 'graphql/language/ast'; -import { print } from 'graphql/language/printer'; -import { separateOperations } from 'graphql/utilities'; + ListValueNode +} from "graphql/language/ast"; +import { print } from "graphql/language/printer"; +import { separateOperations } from "graphql/utilities"; // We'll only fetch the `ListIteratee` type from the `@types/lodash`, but get // `sortBy` from the modularized version of the package to avoid bringing in // all of `lodash`. -import { ListIteratee } from 'lodash'; -import sortBy from 'lodash.sortby'; +import { ListIteratee } from "lodash"; +import sortBy from "lodash.sortby"; // Replace numeric, string, list, and object literals with "empty" // values. Leaves enums alone (since there's no consistent "zero" enum). This @@ -31,20 +31,20 @@ import sortBy from 'lodash.sortby'; export function hideLiterals(ast: DocumentNode): DocumentNode { return visit(ast, { IntValue(node: IntValueNode): IntValueNode { - return { ...node, value: '0' }; + return { ...node, value: "0" }; }, FloatValue(node: FloatValueNode): FloatValueNode { - return { ...node, value: '0' }; + return { ...node, value: "0" }; }, StringValue(node: StringValueNode): StringValueNode { - return { ...node, value: '', block: false }; + return { ...node, value: "", block: false }; }, ListValue(node: ListValueNode): ListValueNode { return { ...node, values: [] }; }, ObjectValue(node: ObjectValueNode): ObjectValueNode { return { ...node, fields: [] }; - }, + } }); } @@ -53,14 +53,14 @@ export function hideLiterals(ast: DocumentNode): DocumentNode { export function hideStringAndNumericLiterals(ast: DocumentNode): DocumentNode { return visit(ast, { IntValue(node: IntValueNode): IntValueNode { - return { ...node, value: '0' }; + return { ...node, value: "0" }; }, FloatValue(node: FloatValueNode): FloatValueNode { - return { ...node, value: '0' }; + return { ...node, value: "0" }; }, StringValue(node: StringValueNode): StringValueNode { - return { ...node, value: '', block: false }; - }, + return { ...node, value: "", block: false }; + } }); } @@ -71,7 +71,7 @@ export function hideStringAndNumericLiterals(ast: DocumentNode): DocumentNode { // the client before sending to the server to save bandwidth and parsing time.) export function dropUnusedDefinitions( ast: DocumentNode, - operationName: string, + operationName: string ): DocumentNode { const separated = separateOperations(ast)[operationName]; if (!separated) { @@ -102,14 +102,14 @@ function sorted( export function sortAST(ast: DocumentNode): DocumentNode { return visit(ast, { OperationDefinition( - node: OperationDefinitionNode, + node: OperationDefinitionNode ): OperationDefinitionNode { return { ...node, variableDefinitions: sorted( node.variableDefinitions, - 'variable.name.value', - ), + "variable.name.value" + ) }; }, SelectionSet(node: SelectionSetNode): SelectionSetNode { @@ -119,34 +119,34 @@ export function sortAST(ast: DocumentNode): DocumentNode { // then FragmentSpread, then InlineFragment. By a lovely coincidence, // the order we want them to appear in is alphabetical by node.kind. // Use sortBy instead of sorted because 'selections' is not optional. - selections: sortBy(node.selections, 'kind', 'name.value'), + selections: sortBy(node.selections, "kind", "name.value") }; }, Field(node: FieldNode): FieldNode { return { ...node, - arguments: sorted(node.arguments, 'name.value'), + arguments: sorted(node.arguments, "name.value") }; }, FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode { - return { ...node, directives: sorted(node.directives, 'name.value') }; + return { ...node, directives: sorted(node.directives, "name.value") }; }, InlineFragment(node: InlineFragmentNode): InlineFragmentNode { - return { ...node, directives: sorted(node.directives, 'name.value') }; + return { ...node, directives: sorted(node.directives, "name.value") }; }, FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode { return { ...node, - directives: sorted(node.directives, 'name.value'), + directives: sorted(node.directives, "name.value"), variableDefinitions: sorted( node.variableDefinitions, - 'variable.name.value', - ), + "variable.name.value" + ) }; }, Directive(node: DirectiveNode): DirectiveNode { - return { ...node, arguments: sorted(node.arguments, 'name.value') }; - }, + return { ...node, arguments: sorted(node.arguments, "name.value") }; + } }); } @@ -159,9 +159,9 @@ export function removeAliases(ast: DocumentNode): DocumentNode { Field(node: FieldNode): FieldNode { return { ...node, - alias: undefined, + alias: undefined }; - }, + } }); } @@ -183,17 +183,17 @@ export function printWithReducedWhitespace(ast: DocumentNode): string { StringValue(node: StringValueNode): StringValueNode { return { ...node, - value: Buffer.from(node.value, 'utf8').toString('hex'), - block: false, + value: Buffer.from(node.value, "utf8").toString("hex"), + block: false }; - }, + } }); const withWhitespace = print(sanitizedAST); const minimizedButStillHex = withWhitespace - .replace(/\s+/g, ' ') + .replace(/\s+/g, " ") .replace(/([^_a-zA-Z0-9]) /g, (_, c) => c) .replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c); return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) => - JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')), + JSON.stringify(Buffer.from(hex, "hex").toString("utf8")) ); } diff --git a/packages/apollo-graphql/tsconfig.json b/packages/apollo-graphql/tsconfig.json index 4fcb63fe14..a684abd825 100644 --- a/packages/apollo-graphql/tsconfig.json +++ b/packages/apollo-graphql/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.base", "compilerOptions": { "rootDir": "./src", - "outDir": "./dist" + "outDir": "./lib" }, "include": ["src/**/*"], "exclude": ["**/__tests__", "**/__mocks__"], diff --git a/packages/apollo-graphql/tsconfig.test.json b/packages/apollo-graphql/tsconfig.test.json new file mode 100644 index 0000000000..9b67f6d530 --- /dev/null +++ b/packages/apollo-graphql/tsconfig.test.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.test.base", + "include": ["**/__tests__/*", "**/__mocks__/*"] +} diff --git a/tsconfig.json b/tsconfig.json index b2dfd63d68..1f3e5b9a80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,9 @@ { "path": "./packages/apollo-codegen-scala" }, { "path": "./packages/apollo-codegen-swift" }, { "path": "./packages/apollo-codegen-typescript" }, + { "path": "./packages/apollo-graphql" }, { "path": "./packages/apollo-language-server" }, { "path": "./packages/vscode-apollo" }, - { "path": "./packages/apollo-tools" }, + { "path": "./packages/apollo-tools" } ] } From 804a76fefcbe952b5ebc4aea140933538d4b1f64 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Wed, 13 Feb 2019 17:13:32 -0800 Subject: [PATCH 10/12] Re-allow lodash.sortby via package level esModuleInterop --- package-lock.json | 14 +++++++++++--- package.json | 1 + packages/apollo-graphql/tsconfig.json | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index db41150244..ce16b5aef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1764,6 +1764,15 @@ "integrity": "sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw==", "dev": true }, + "@types/lodash.sortby": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/@types/lodash.sortby/-/lodash.sortby-4.7.5.tgz", + "integrity": "sha512-jvkna2KsynP6jcpsDeZNCOyhHQOIXfq/WAbHm6wjYGQLHI4HPzlMiVaGu8GkrhZLfC3Uj8Ctogc8NuyBXdOHVA==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", @@ -2182,7 +2191,7 @@ "apollo-graphql": { "version": "file:packages/apollo-graphql", "requires": { - "lodash": "^4.7.0" + "lodash.sortby": "^4.7.0" } }, "apollo-language-server": { @@ -9733,8 +9742,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "lodash.template": { "version": "4.4.0", diff --git a/package.json b/package.json index 21eec444b0..70639b2c9e 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/jest": "23.3.14", "@types/listr": "0.13.0", "@types/lodash": "4.14.120", + "@types/lodash.sortby": "^4.7.5", "@types/minimatch": "3.0.3", "@types/nock": "9.3.1", "@types/node": "10.12.26", diff --git a/packages/apollo-graphql/tsconfig.json b/packages/apollo-graphql/tsconfig.json index a684abd825..bb4dfd0ed8 100644 --- a/packages/apollo-graphql/tsconfig.json +++ b/packages/apollo-graphql/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../../tsconfig.base", "compilerOptions": { "rootDir": "./src", - "outDir": "./lib" + "outDir": "./lib", + "esModuleInterop": true, + "target": "es2016" }, "include": ["src/**/*"], "exclude": ["**/__tests__", "**/__mocks__"], From bf6e3e1592200850ba3a0652675d2233a22f3416 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 14 Feb 2019 12:33:01 +0200 Subject: [PATCH 11/12] Fix tests for `apollo-graphql` by adding `jest` config. I observed that the tests weren't running for the `apollo-graphql` package when reviewing the work in #1018. Upon investigating further (via `jest --debug`), it seems that the Jest configuration was missing the appropriate configuration for it to pickup the extensions of `ts` and `tsx` within the `apollo-graphql` repository. It was only then that I realized that this repository still has `jest` configuration defined in each of its `package.json` files, rather than a monorepo-root `jest.config.base.json` file. That's fine, but it caught me off guard. I do think that we should try to DRY-up this repetition and align this across teams and repositories as soon as possible because it's difficult to start building per-package exceptions which will quickly become hard to manage. For now, this will get tests working for `apollo-graphql` within the scope of the PR that brings it into the `apollo-tooling` repository, which seems necessary to tick the box for shipping that PR. --- packages/apollo-graphql/package.json | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/apollo-graphql/package.json b/packages/apollo-graphql/package.json index e9391fa94b..50be385d5f 100644 --- a/packages/apollo-graphql/package.json +++ b/packages/apollo-graphql/package.json @@ -15,5 +15,35 @@ }, "peerDependencies": { "graphql": "^14.0.2" + }, + "jest": { + "preset": "ts-jest", + "transformIgnorePatterns": [ + "/node_modules/", + "/apollo-env/" + ], + "testEnvironment": "node", + "testMatch": [ + "**/__tests__/*.(js|ts)" + ], + "setupFiles": [ + "/../apollo-env/lib/index.js" + ], + "testPathIgnorePatterns": [ + "/node_modules/", + "/lib/", + "/test/fixtures/", + "/test/test-utils" + ], + "moduleFileExtensions": [ + "ts", + "js" + ], + "globals": { + "ts-jest": { + "tsConfig": "/tsconfig.test.json", + "diagnostics": false + } + } } } From bee8eaa2b20f79c974c68ed764a50736771d4a2f Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 14 Feb 2019 12:10:13 +0200 Subject: [PATCH 12/12] Add `esModuleInterop: true` to `apollo-graphql`'s `tsconfig.test.json`. Tests for `apollo-graphql` are working again as of this commit, but the tests highlighted interop failures with regards to `esModuleInterop` since the testing configuration for this repository seems to be partially configured to use different configurations. We should probably fix that! Since this setting isn't inherited from a monorepo root configuration, this needs to be defined in both the package's `tsconfig.json` and its `tsconfig.test.json`. --- packages/apollo-graphql/tsconfig.test.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/apollo-graphql/tsconfig.test.json b/packages/apollo-graphql/tsconfig.test.json index 9b67f6d530..bcaa6d0750 100644 --- a/packages/apollo-graphql/tsconfig.test.json +++ b/packages/apollo-graphql/tsconfig.test.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.test.base", + "compilerOptions": { + "esModuleInterop": true, + }, "include": ["**/__tests__/*", "**/__mocks__/*"] }