-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(@graphql-hive/cli): json output #6122
base: main
Are you sure you want to change the base?
Changes from all commits
7dc21e7
de6c0dc
b5f78b4
31eb025
27e8811
195e466
3328e20
f81c69c
2423a50
d4bd1bf
35da5f6
fbc286e
6a4a46b
1e3c14f
8f68b79
c9628bb
528bad9
e8b65ec
fe15732
0d5d33f
333095c
79f7763
1fbbcc7
cf6074c
f8986a6
754526c
321325d
923c422
032ff42
ba14c2d
ad7e65c
d4f1b88
392ad0f
6b9aba5
af8721b
def2053
d631f8b
219faf5
58b62b8
dee2049
2221fa0
6d498eb
b258db2
97c46dd
9439aa7
612aaa4
7c1823e
3ccd617
a45f5d6
84833da
2b8c7a8
888657a
87fe7c7
4d5644f
7dffe2e
b33e865
5796094
247cf5c
35efce1
b0e966e
45af397
e15c9cf
4861b9e
eb0b1e1
609d286
1fa5079
4b3675f
3010a25
58d3c70
9d0fc87
31cdc7c
76eb192
5489204
8b23d0e
77db99a
59b066c
2e675fd
438941c
ee1dcc7
044835a
ba94eea
cd30c27
90afcf7
d9b114d
728af1f
8cf2893
764fb20
64d60d8
028cfde
4fb7f07
07fa446
5d62044
a503c12
35e299e
fd1a85c
c35833d
07b9f97
5049eb3
b774dcf
b3b63d2
f7e3ef0
5444780
af41791
48973fb
f1f6b9f
7943697
f6b12a4
c7c629e
1cd5734
ada91d0
ade8ad3
ad3024b
1f2f2eb
c8dbfaf
ed962e7
3d6b2e7
ae88819
66d174c
d2bfa72
dae187d
6663144
45362ae
71d1bd0
bda183b
33e5025
aaf34ac
2457c33
c5be266
0b7aff4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,14 @@ | |
"@aws-sdk/client-s3": "3.693.0", | ||
"@esm2cjs/execa": "6.1.1-cjs.1", | ||
"@graphql-hive/apollo": "workspace:*", | ||
"@graphql-hive/cli": "workspace:*", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to get our coverage to not be child-process based because each CLI invocation is taking 1-2 seconds which is terrible. Ideally we can reserve child process spawning tests for only a select few cases that really need that. I briefly played around with that while waiting for CI a few times. Currently this dep remains unused, we could remove. |
||
"@graphql-hive/core": "workspace:*", | ||
"@graphql-typed-document-node/core": "3.2.0", | ||
"@hive/rate-limit": "workspace:*", | ||
"@hive/schema": "workspace:*", | ||
"@hive/server": "workspace:*", | ||
"@hive/storage": "workspace:*", | ||
"@sinclair/typebox": "^0.34.12", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having worked with this library for the first time for this PR via prompt from @n1ru4l, I can now confirm I like it. |
||
"@trpc/client": "10.45.2", | ||
"@trpc/server": "10.45.2", | ||
"@types/async-retry": "1.4.8", | ||
|
@@ -34,9 +36,9 @@ | |
"human-id": "4.1.1", | ||
"ioredis": "5.4.1", | ||
"slonik": "30.4.4", | ||
"strip-ansi": "7.1.0", | ||
"strip-ansi": "6.0.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For our CLI output snapshot serializer. |
||
"tslib": "2.8.1", | ||
"vitest": "2.0.5", | ||
"vitest": "2.1.8", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thought this might be the version that improves terminal watch mode flickering. Doesn't seem to have worked but no matter, upgrade vitest all the same. |
||
"zod": "3.23.8" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,27 @@ | ||
import { randomUUID } from 'node:crypto'; | ||
import { writeFile } from 'node:fs/promises'; | ||
import { tmpdir } from 'node:os'; | ||
import { join, resolve } from 'node:path'; | ||
import { resolve } from 'node:path'; | ||
import { execaCommand } from '@esm2cjs/execa'; | ||
import { fetchLatestSchema, fetchLatestValidSchema } from './flow'; | ||
import { generateTmpFile } from './fs'; | ||
import { getServiceHost } from './utils'; | ||
|
||
const binPath = resolve(__dirname, '../../packages/libraries/cli/bin/run'); | ||
const cliDir = resolve(__dirname, '../../packages/libraries/cli'); | ||
|
||
async function generateTmpFile(content: string, extension: string) { | ||
const dir = tmpdir(); | ||
const fileName = randomUUID(); | ||
const filepath = join(dir, `${fileName}.${extension}`); | ||
|
||
await writeFile(filepath, content, 'utf-8'); | ||
|
||
return filepath; | ||
} | ||
|
||
async function exec(cmd: string) { | ||
const outout = await execaCommand(`${binPath} ${cmd}`, { | ||
export async function exec(cmd: string) { | ||
const result = await execaCommand(`${binPath} ${cmd}`, { | ||
shell: true, | ||
env: { | ||
OCLIF_CLI_CUSTOM_PATH: cliDir, | ||
NODE_OPTIONS: '--no-deprecation', | ||
}, | ||
}); | ||
|
||
if (outout.failed) { | ||
throw new Error(outout.stderr); | ||
if (result.failed) { | ||
throw new Error('CLI execution marked as "failed".', { cause: result.stderr }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While trying to understand where stack traces and errors were coming from, I adjusted this to be more explicit. |
||
} | ||
|
||
return outout.stdout; | ||
return result.stdout; | ||
} | ||
|
||
export async function schemaPublish(args: string[]) { | ||
|
@@ -70,6 +59,8 @@ async function dev(args: string[]) { | |
); | ||
} | ||
|
||
export type CLI = ReturnType<typeof createCLI>; | ||
|
||
export function createCLI(tokens: { readwrite: string; readonly: string }) { | ||
let publishCount = 0; | ||
|
||
|
@@ -81,6 +72,7 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
expect: expectedStatus, | ||
legacy_force, | ||
legacy_acceptBreakingChanges, | ||
json, | ||
}: { | ||
sdl: string; | ||
commit?: string; | ||
|
@@ -90,6 +82,7 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
legacy_force?: boolean; | ||
legacy_acceptBreakingChanges?: boolean; | ||
expect: 'latest' | 'latest-composable' | 'ignored' | 'rejected'; | ||
json?: boolean; | ||
}): Promise<string> { | ||
const publishName = ` #${++publishCount}`; | ||
const commit = randomUUID(); | ||
|
@@ -106,6 +99,7 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
...(metadata ? ['--metadata', await generateTmpFile(JSON.stringify(metadata), 'json')] : []), | ||
...(legacy_force ? ['--force'] : []), | ||
...(legacy_acceptBreakingChanges ? ['--experimental_acceptBreakingChanges'] : []), | ||
...(json ? ['--json'] : []), | ||
await generateTmpFile(sdl, 'graphql'), | ||
]); | ||
|
||
|
@@ -177,15 +171,18 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
sdl, | ||
serviceName, | ||
expect: expectedStatus, | ||
json, | ||
}: { | ||
sdl: string; | ||
serviceName?: string; | ||
expect: 'approved' | 'rejected'; | ||
json?: boolean; | ||
}): Promise<string> { | ||
const cmd = schemaCheck([ | ||
'--registry.accessToken', | ||
tokens.readonly, | ||
...(serviceName ? ['--service', serviceName] : []), | ||
...(json ? ['--json'] : []), | ||
await generateTmpFile(sdl, 'graphql'), | ||
]); | ||
|
||
|
@@ -199,11 +196,19 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
async function deleteCommand({ | ||
serviceName, | ||
expect: expectedStatus, | ||
json, | ||
}: { | ||
serviceName?: string; | ||
json?: boolean; | ||
expect: 'latest' | 'latest-composable' | 'rejected'; | ||
}): Promise<string> { | ||
const cmd = schemaDelete(['--token', tokens.readwrite, '--confirm', serviceName ?? '']); | ||
const cmd = schemaDelete([ | ||
'--token', | ||
tokens.readwrite, | ||
'--confirm', | ||
serviceName ?? '', | ||
...(json ? ['--json'] : []), | ||
]); | ||
|
||
const before = { | ||
latest: await fetchLatestSchema(tokens.readonly).then(r => r.expectNoGraphQLErrors()), | ||
|
@@ -259,11 +264,13 @@ export function createCLI(tokens: { readwrite: string; readonly: string }) { | |
url: string; | ||
sdl: string; | ||
}>; | ||
json?: boolean; | ||
remote: boolean; | ||
write?: string; | ||
useLatestVersion?: boolean; | ||
}) { | ||
return dev([ | ||
...(input.json ? ['--json'] : []), | ||
...(input.remote | ||
? [ | ||
'--remote', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { randomUUID } from 'node:crypto'; | ||
import { readFile, writeFile } from 'node:fs/promises'; | ||
import { tmpdir } from 'node:os'; | ||
import { join } from 'node:path'; | ||
|
||
export function tmpFile(extension: string) { | ||
const dir = tmpdir(); | ||
const fileName = randomUUID(); | ||
const filepath = join(dir, `${fileName}.${extension}`); | ||
|
||
return { | ||
filepath, | ||
read() { | ||
return readFile(filepath, 'utf-8'); | ||
}, | ||
}; | ||
} | ||
|
||
export async function generateTmpFile(content: string, extension: string) { | ||
const dir = tmpdir(); | ||
const fileName = randomUUID(); | ||
const filepath = join(dir, `${fileName}.${extension}`); | ||
|
||
await writeFile(filepath, content, 'utf-8'); | ||
|
||
return filepath; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* This module uses Vitest's fixture system to make common usage patterns | ||
* of our testkit easily consumable in test cases. @see https://vitest.dev/guide/test-context.html#test-extend | ||
*/ | ||
|
||
import { test as testBase } from 'vitest'; | ||
import { CLI, createCLI } from './cli'; | ||
import { ProjectType } from './gql/graphql'; | ||
import { initSeed, OrgSeed, OwnerSeed, ProjectSeed, Seed, TargetAccessTokenSeed } from './seed'; | ||
|
||
interface Context { | ||
seed: Seed; | ||
owner: OwnerSeed; | ||
org: OrgSeed; | ||
// | ||
// "single" branch | ||
// | ||
projectSingle: ProjectSeed; | ||
targetAccessTokenSingle: TargetAccessTokenSeed; | ||
cliSingle: CLI; | ||
// | ||
// "federation" branch | ||
// | ||
projectFederation: ProjectSeed; | ||
targetAccessTokenFederation: TargetAccessTokenSeed; | ||
cliFederation: CLI; | ||
// | ||
// "stitching" branch | ||
// | ||
projectStitching: ProjectSeed; | ||
targetAccessTokenStitching: TargetAccessTokenSeed; | ||
cliStitching: CLI; | ||
} | ||
|
||
export const test = testBase.extend<Context>({ | ||
seed: async ({}, use) => { | ||
const seed = await initSeed(); | ||
await use(seed); | ||
}, | ||
owner: async ({ seed }, use) => { | ||
const owner = await seed.createOwner(); | ||
await use(owner); | ||
}, | ||
org: async ({ owner }, use) => { | ||
const org = await owner.createOrg(); | ||
await use(org); | ||
}, | ||
// | ||
// "single" branch | ||
// | ||
projectSingle: async ({ org }, use) => { | ||
const project = await org.createProject(ProjectType.Single); | ||
await use(project); | ||
}, | ||
targetAccessTokenSingle: async ({ projectSingle }, use) => { | ||
const targetAccessToken = await projectSingle.createTargetAccessToken({}); | ||
await use(targetAccessToken); | ||
}, | ||
cliSingle: async ({ targetAccessTokenSingle }, use) => { | ||
const cli = createCLI({ | ||
readwrite: targetAccessTokenSingle.secret, | ||
readonly: targetAccessTokenSingle.secret, | ||
}); | ||
await use(cli); | ||
}, | ||
// | ||
// "federation" branch | ||
// | ||
projectFederation: async ({ org }, use) => { | ||
const project = await org.createProject(ProjectType.Federation); | ||
await use(project); | ||
}, | ||
targetAccessTokenFederation: async ({ projectFederation }, use) => { | ||
const targetAccessToken = await projectFederation.createTargetAccessToken({}); | ||
await use(targetAccessToken); | ||
}, | ||
cliFederation: async ({ targetAccessTokenFederation }, use) => { | ||
const cli = createCLI({ | ||
readwrite: targetAccessTokenFederation.secret, | ||
readonly: targetAccessTokenFederation.secret, | ||
}); | ||
await use(cli); | ||
}, | ||
// | ||
// "stitching" branch | ||
// | ||
projectStitching: async ({ org }, use) => { | ||
const project = await org.createProject(ProjectType.Stitching); | ||
await use(project); | ||
}, | ||
targetAccessTokenStitching: async ({ projectStitching }, use) => { | ||
const targetAccessToken = await projectStitching.createTargetAccessToken({}); | ||
await use(targetAccessToken); | ||
}, | ||
cliStitching: async ({ targetAccessTokenStitching }, use) => { | ||
const cli = createCLI({ | ||
readwrite: targetAccessTokenStitching.secret, | ||
readonly: targetAccessTokenStitching.secret, | ||
}); | ||
await use(cli); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './cli-output'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * as SnapshotSerializers from './_'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes stack traces to be rendered by OClif which breaks our CLI snapshot tests.