Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
453 changes: 46 additions & 407 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/apollo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
"apollo-codegen-scala": "file:../apollo-codegen-scala",
"apollo-codegen-swift": "file:../apollo-codegen-swift",
"apollo-codegen-typescript": "file:../apollo-codegen-typescript",
"apollo-engine-reporting": "0.2.2",
"apollo-env": "file:../apollo-env",
"apollo-graphql": "file:../apollo-graphql",
"apollo-language-server": "file:../apollo-language-server",
"chalk": "2.4.2",
"cli-ux": "4.9.3",
Expand Down
95 changes: 29 additions & 66 deletions packages/apollo/src/commands/client/extract.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
import { createHash } from "crypto";
import { writeFileSync } from "fs";
import {
printWithReducedWhitespace,
sortAST,
defaultSignature as engineDefaultSignature
} from "apollo-engine-reporting";
import { DocumentNode } from "graphql";

import { ClientCommand } from "../../Command";
import { hideCertainLiterals } from "./push";

// XXX this is duplicated code
const manifestOperationHash = (str: string): string =>
createHash("sha256")
.update(str)
.digest("hex");

const engineSignature = (_TODO_operationAST: DocumentNode): string => {
// TODO. We don't currently have access to the operation name since it's
// currently omitted by the `apollo-codegen-core` package logic.
return engineDefaultSignature(_TODO_operationAST, "TODO");
};
import {
getOperationManifestFromProject,
ManifestEntry
} from "../../utils/getOperationManifestFromProject";
import { ClientIdentity } from "apollo-language-server";

export default class ClientExtract extends ClientCommand {
static description = "Extract queries from a client";
Expand All @@ -38,52 +22,31 @@ export default class ClientExtract extends ClientCommand {
];

async run() {
const { clientIdentity, operations, filename }: any = await this.runTasks(
({ flags, project, config, args }) => [
{
title: "Extracting operations from project",
task: async ctx => {
const operations = Object.values(
this.project.mergedOperationsAndFragmentsForService
).map(operationAST => {
// While this could include dropping unused definitions, they are
// kept because the registered operations should mirror those in the
// client bundle minus any PII which lives within string literals.
const printed = printWithReducedWhitespace(
sortAST(hideCertainLiterals(operationAST))
);

return {
signature: manifestOperationHash(printed),
document: printed,
metadata: {
engineSignature: engineSignature(operationAST)
}
};
});

ctx.operations = operations;
ctx.clientIdentity = config.client;
}
},
{
title: "Outputing extracted queries",
task: (ctx, task) => {
const filename = args.output;
task.title = "Outputing extracted queries to " + filename;
ctx.filename = filename;
writeFileSync(
filename,
JSON.stringify(
{ version: 1, operations: ctx.operations },
null,
2
)
);
}
const { clientIdentity, operations, filename } = await this.runTasks<{
Comment thread
trevor-scheer marked this conversation as resolved.
clientIdentity: ClientIdentity;
operations: ManifestEntry[];
filename: string;
}>(({ flags, project, config, args }) => [
Comment thread
trevor-scheer marked this conversation as resolved.
{
title: "Extracting operations from project",
task: async ctx => {
ctx.operations = getOperationManifestFromProject(this.project);
ctx.clientIdentity = config.client;
}
]
);
},
{
title: "Outputing extracted queries",
task: (ctx, task) => {
const filename = args.output;
task.title = "Outputing extracted queries to " + filename;
ctx.filename = filename;
writeFileSync(
filename,
JSON.stringify({ version: 2, operations: ctx.operations }, null, 2)
);
}
}
]);

this.log(
`Successfully wrote ${operations.length} operations from the ${
Expand Down
86 changes: 15 additions & 71 deletions packages/apollo/src/commands/client/push.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,9 @@
import { createHash } from "crypto";
import {
printWithReducedWhitespace,
sortAST,
defaultSignature as engineDefaultSignature
} from "apollo-engine-reporting";

import {
visit,
DocumentNode,
IntValueNode,
FloatValueNode,
StringValueNode
} from "graphql";

import { ClientCommand } from "../../Command";

const manifestOperationHash = (str: string): string =>
createHash("sha256")
.update(str)
.digest("hex");

const engineSignature = (_TODO_operationAST: DocumentNode): string => {
// TODO. We don't currently have access to the operation name since it's
// currently omitted by the `apollo-codegen-core` package logic.
return engineDefaultSignature(_TODO_operationAST, "TODO");
};

// In the same spirit as the similarly named `hideLiterals` function from the
// `apollo-engine-reporting/src/signature.ts` module, we'll do an AST visit
// to redact literals. Developers are strongly encouraged to use the
// `variables` aspect of the which would avoid these being explicitly
// present in the operation manifest at all. The primary area of concern here
// is to avoid sending in-lined literals which might contain sensitive
// information (e.g. API keys, etc.).
export function hideCertainLiterals(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 };
}
});
}
import {
getOperationManifestFromProject,
ManifestEntry
} from "../../utils/getOperationManifestFromProject";
import { ClientIdentity } from "apollo-language-server";

export default class ServicePush extends ClientCommand {
static description = "Push a service to Engine";
Expand All @@ -54,35 +12,21 @@ export default class ServicePush extends ClientCommand {
};

async run() {
const {
clientIdentity,
operations,
serviceName
}: any = await this.runTasks(({ flags, project, config }) => [
const { clientIdentity, operations, serviceName } = await this.runTasks<{
clientIdentity: ClientIdentity;
operations: ManifestEntry[];
serviceName: string;
}>(({ flags, project, config }) => [
{
title: "Pushing client information to Engine",
task: async ctx => {
if (!config.name) {
throw new Error("No service found to link to Engine");
}
const operations = Object.values(
this.project.mergedOperationsAndFragmentsForService
).map(operationAST => {
// While this could include dropping unused definitions, they are
// kept because the registered operations should mirror those in the
// client bundle minus any PII which lives within string literals.
const printed = printWithReducedWhitespace(
sortAST(hideCertainLiterals(operationAST))
);

return {
signature: manifestOperationHash(printed),
document: printed,
metadata: {
engineSignature: engineSignature(operationAST)
}
};
});
const operationManifest = getOperationManifestFromProject(
this.project
);

const { name, referenceID, version } = config.client!;
if (!name) {
Expand All @@ -96,13 +40,13 @@ export default class ServicePush extends ClientCommand {
version
},
id: config.name,
operations
operations: operationManifest
};

await project.engine.registerOperations(variables);

// store data for logging
ctx.operations = operations;
ctx.operations = operationManifest;
ctx.serviceName = variables.id;
ctx.clientIdentity = variables.clientIdentity;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`getOperationManifestFromProject builds an operation manifest 1`] = `
Array [
Object {
"document": "query SchemaTagsAndFieldStats($id:ID!){service(id:$id){__typename schemaTags{__typename tag}stats(from:\\"\\",to:\\"\\"){__typename fieldStats{__typename groupBy{__typename field}metrics{__typename fieldHistogram{__typename durationMs(percentile:0)}}}}}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "135e8314de0c2f23f4a2be87b20e59f30ecf2cbcdfad6676a46aad21d29cdb5b",
},
Object {
"document": "mutation CheckSchema($frontend:String,$gitContext:GitContextInput,$historicParameters:HistoricQueryParameters,$id:ID!,$schema:IntrospectionSchemaInput!,$tag:String){service(id:$id){__typename checkSchema(baseSchemaTag:$tag,frontend:$frontend,gitContext:$gitContext,historicParameters:$historicParameters,proposedSchema:$schema){__typename diffToPrevious{__typename changes{__typename code description type}type validationConfig{__typename from queryCountThreshold queryCountThresholdPercentage to}}targetUrl}}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "3fd18a0f0369d801024bb42268bc4d4d1f6edd84a412923c4678c026bc1bdc62",
},
Object {
"document": "mutation RegisterOperations($clientIdentity:RegisteredClientIdentityInput!,$id:ID!,$operations:[RegisteredOperationInput!]!){service(id:$id){__typename registerOperations(clientIdentity:$clientIdentity,operations:$operations)}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "1428ab44b0f2f4ea25bc28c08b5a7235cb96a2d535d1a15e0edd5f0695a4903f",
},
Object {
"document": "mutation UploadSchema($gitContext:GitContextInput,$id:ID!,$schema:IntrospectionSchemaInput!,$tag:String!){service(id:$id){__typename uploadSchema(gitContext:$gitContext,schema:$schema,tag:$tag){__typename code message success tag{__typename schema{__typename hash}tag}}}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "a2497bb8c32f97870dfc58823b1377f6339c3e5ba53010b2a6b1e5d8fa8b8634",
},
Object {
"document": "mutation ValidateOperations($gitContext:GitContextInput,$id:ID!,$operations:[OperationDocumentInput!]!,$tag:String){service(id:$id){__typename validateOperations(gitContext:$gitContext,operations:$operations,tag:$tag){__typename validationResults{__typename code description operation{__typename name}type}}}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "600e261efe1f25ee30c53edd0b2b83c7eb6691ef5ae1c8f460ace2d303ad053d",
},
Object {
"document": "fragment IntrospectionFullType on IntrospectionType{__typename description enumValues(includeDeprecated:true){__typename depreactionReason description isDeprecated name}fields{__typename args{__typename...IntrospectionInputValue}deprecationReason description isDeprecated name type{__typename...IntrospectionTypeRef}}inputFields{__typename...IntrospectionInputValue}interfaces{__typename...IntrospectionTypeRef}kind name possibleTypes{__typename...IntrospectionTypeRef}}fragment IntrospectionInputValue on IntrospectionInputValue{__typename defaultValue description name type{__typename...IntrospectionTypeRef}}fragment IntrospectionTypeRef on IntrospectionType{__typename kind name ofType{__typename kind name ofType{__typename kind name ofType{__typename kind name ofType{__typename kind name ofType{__typename kind name ofType{__typename kind name ofType{__typename kind name}}}}}}}}query GetSchemaByTag($tag:String!){service:me{__typename...on Service{schema(tag:$tag){__typename hash __schema:introspection{__typename directives{__typename args{__typename...IntrospectionInputValue}description locations name}mutationType{__typename name}queryType{__typename name}subscriptionType{__typename name}types{__typename...IntrospectionFullType}}}}}}",
"metadata": Object {
"engineSignature": "",
},
"signature": "f7f71dac7423a856fcc1b05a944e92247d0bba4beafd508410890242d4cf6d5f",
},
]
`;
Loading