Skip to content

Commit

Permalink
Merge pull request #5143 from rmosolgo/sync-dump-payload
Browse files Browse the repository at this point in the history
Add sync --dump-payload option
  • Loading branch information
rmosolgo authored Nov 4, 2024
2 parents dc23065 + 9c89827 commit f7755bd
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tmp/*
node_modules/
yarn.lock
OperationStoreClient.js
DumpPayloadExample.json
spec/integration/tmp
.vscode/
*.swp
Expand Down
1 change: 1 addition & 0 deletions guides/javascript_client/sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ option | description
`--add-typename` | Add `__typename` to all selection sets (for use with Apollo Client)
`--verbose` | Output some debug information
`--changeset-version` | Set a {% internal_link "Changeset Version", "/changesets/installation#controller-setup" %} when syncing these queries. (`context[:changeset_version]` will also be required at runtime, when running these stored operations.)
`--dump-payload` | A file to write the HTTP Post payload into, or if no filename is passed, then the payload will be written to stdout.

You can see these and a few others with `graphql-ruby-client sync --help`.

Expand Down
32 changes: 32 additions & 0 deletions javascript_client/src/__tests__/cliTest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var childProcess = require("child_process")
let fs = require('fs')

describe("CLI", () => {
it("exits 1 on error", () => {
Expand All @@ -22,4 +23,35 @@ describe("CLI", () => {
var response = buffer.toString().replace(/\033\[[0-9;]*m/g, "")
expect(response).toEqual("No URL; Generating artifacts without syncing them\nGenerating client module in src/OperationStoreClient.js...\n✓ Done!\n")
})

it("writes to a dump file", () => {
let buffer = childProcess.execSync("node ./cli.js sync --client=something --header=Ab-cd:ef-gh --dump-payload=./DumpPayloadExample.json --path=\"**/doc1.graphql\"", {stdio: "pipe"})
console.log(buffer.toString())
let dumpedJSON = fs.readFileSync("./DumpPayloadExample.json", 'utf8')
expect(dumpedJSON).toEqual(`{
"operations": [
{
"name": "GetStuff",
"body": "query GetStuff {\\n stuff\\n}",
"alias": "b8086942c2fbb6ac69b97cbade848033"
}
]
}
`)
})

it("writes to stdout", () => {
let buffer = childProcess.execSync("node ./cli.js sync --client=something --header=Ab-cd:ef-gh --dump-payload --path=\"**/doc1.graphql\"", {stdio: "pipe"})
let dumpedJSON = buffer.toString().replace(/\033\[[0-9;]*m/g, "")
expect(dumpedJSON).toEqual(`{
"operations": [
{
"name": "GetStuff",
"body": "query GetStuff {\\n stuff\\n}",
"alias": "b8086942c2fbb6ac69b97cbade848033"
}
]
}
`)
})
})
13 changes: 10 additions & 3 deletions javascript_client/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import parseArgs from "minimist"
import sync from "./sync/index"
import sync, { SyncOptions } from "./sync/index"
var argv = parseArgs(process.argv.slice(2))

if (argv.help || argv.h) {
Expand Down Expand Up @@ -38,6 +38,7 @@ optional arguments:
(may be repeated)
--changeset-version=<version> Populates \`context[:changeset_version]\` for this sync (for the GraphQL-Enterprise "Changesets" feature)
--add-typename Automatically adds the "__typename" field to your queries
--dump-payload=<filename> Print the HTTP Post data to this file, or to stdout if no filename is given
--quiet Suppress status logging
--verbose Print debug output
--help Print this message
Expand All @@ -60,7 +61,7 @@ optional arguments:
})
}
}
var result = sync({
let syncOptions: SyncOptions = {
path: argv.path,
relayPersistedOutput: argv["relay-persisted-output"],
apolloCodegenJsonOutput: argv["apollo-codegen-json-output"],
Expand All @@ -77,7 +78,13 @@ optional arguments:
quiet: argv.hasOwnProperty("quiet"),
verbose: argv.hasOwnProperty("verbose"),
changesetVersion: argv["changeset-version"],
})
}

if ("dump-payload" in argv) {
syncOptions.dumpPayload = argv["dump-payload"]
}

var result = sync(syncOptions)

result.then(function() {
process.exit(0)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions javascript_client/src/sync/__tests__/dumpPayloadTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import dumpPayload from "../dumpPayload"
import fs from 'fs'
interface MockedObject {
mock: { calls: object }
}

describe("printing out the HTTP Post payload", () => {
beforeEach(() => {
process.stdout.write = jest.fn()
})

afterEach(() => {
jest.clearAllMocks();
})


it("prints the result to stdout", () => {
var spy = (process.stdout.write as unknown) as MockedObject
dumpPayload({"ok": { "1": true}}, { dumpPayload: true })
expect(spy.mock.calls).toMatchSnapshot()
})

it("writes the result to a file", () => {
dumpPayload({"ok": { "1": true}}, {dumpPayload: "./DumpPayloadExample.json"})
let writtenContents = fs.readFileSync("./DumpPayloadExample.json", 'utf8')
expect(writtenContents).toEqual(`{
"ok": {
"1": true
}
}
`)
})
})
14 changes: 14 additions & 0 deletions javascript_client/src/sync/dumpPayload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import fs from 'fs';

interface DumpPayloadOptions {
dumpPayload: string | true,
}

export default function dumpPayload(payload: Object, options: DumpPayloadOptions) {
let payloadStr = JSON.stringify(payload, null, 2) + "\n"
if (options.dumpPayload == true) {
process.stdout.write(payloadStr)
} else {
fs.writeFileSync(options.dumpPayload, payloadStr, 'utf8')
}
}
28 changes: 17 additions & 11 deletions javascript_client/src/sync/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import sendPayload from "./sendPayload"

import dumpPayload from "./dumpPayload"
import { generateClientCode, gatherOperations, ClientOperation } from "./generateClient"
import Logger from "./logger"
import fs from "fs"
import { removeClientFieldsFromString } from "./removeClientFields"
import preparePersistedQueryList from "./preparePersistedQueryList"

interface SyncOptions {
export interface SyncOptions {
path?: string,
relayPersistedOutput?: string,
apolloAndroidOperationOutput?: string,
Expand All @@ -15,6 +15,7 @@ interface SyncOptions {
secret?: string
url?: string,
mode?: string,
dumpPayload?: string | true,
outfile?: string,
outfileType?: string,
client: string,
Expand Down Expand Up @@ -46,28 +47,30 @@ interface SyncOptions {
* @param {Function} options.hash - A custom hash function for query strings with the signature `options.hash(string) => digest` (Default is `md5(string) => digest`)
* @param {Boolean} options.verbose - If true, log debug output
* @param {Object<String, String>} options.headers - If present, extra headers to add to the HTTP request
* @param {String|true} options.dumpPayload - If a filename is given, write the HTTP Post data to that file. If present without a filename, print it to stdout.
* @param {String} options.changesetVersion - If present, sent to populate `context[:changeset_version]` on the server
* @return {Promise} Rejects with an Error or String if something goes wrong. Resolves with the operation payload if successful.
*/
function sync(options: SyncOptions) {
var logger = new Logger(!!options.quiet)
var verbose = !!options.verbose
var url = options.url
if (!url) {
var dumpingPayload = "dumpPayload" in options
if (!url && !dumpingPayload) {
logger.log("No URL; Generating artifacts without syncing them")
}
var clientName = options.client
if (!clientName) {
throw new Error("Client name must be provided for sync")
}
var encryptionKey = options.secret
if (encryptionKey) {
if (encryptionKey && options.dumpPayload != null) {
logger.log("Authenticating with HMAC")
}

var graphqlGlob = options.path
var hashFunc = options.hash
var sendFunc = options.send || sendPayload
var sendFunc = options.send || (dumpingPayload ? dumpPayload : sendPayload)
var gatherMode = options.mode
var clientType = options.outfileType
if (options.relayPersistedOutput) {
Expand Down Expand Up @@ -144,11 +147,7 @@ function sync(options: SyncOptions) {
logger.log("No operations found in " + options.path + ", not syncing anything")
resolve(null)
return
} else if(!url) {
// This is a local-only run to generate an artifact
resolve(payload)
return
} else {
} else if (url) {
logger.log("Syncing " + payload.operations.length + " operations to " + logger.bright(url) + "...")
var sendOpts = {
url: url,
Expand Down Expand Up @@ -220,14 +219,21 @@ function sync(options: SyncOptions) {
reject(err)
return
})
} else if (dumpingPayload) {
sendFunc(payload, { dumpPayload: options.dumpPayload })
return
} else {
// This is a local-only run to generate an artifact
resolve(payload)
return
}
})

return syncPromise.then(function(_payload) {
// The payload is yielded when sync was successful, but typescript had
// trouble using it from ^^ here. So instead, just use its presence as a signal to continue.

// Don't generate a new file when we're using relay-comipler's --persist-output
// Don't generate a new file when we're using relay-compiler's --persist-output
if (_payload && outfile) {
var generatedCode = generateClientCode(clientName, payload.operations, clientType)
var finishedPayload = {
Expand Down

0 comments on commit f7755bd

Please sign in to comment.