Skip to content
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(cli): add generate schema command #120

Merged
merged 2 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions .changeset/four-lies-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gql.tada/cli-utils': patch
---

Add `generate-schema` command which takes a URL | path to a JSON file and outputs a graphql schema. Example: `gql-tada generate-schema https://example.com ./schema.graphql --header 'authorization: bearer token'`
117 changes: 80 additions & 37 deletions packages/cli-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,101 @@
import sade from 'sade';
import { promises as fs, existsSync } from 'node:fs';
import path from 'node:path';
import path, { resolve } from 'node:path';
// We use comment-json to parse the tsconfig as the default ones
// have comment annotations in JSON.
import { parse } from 'comment-json';
import type { IntrospectionQuery } from 'graphql';
import { buildClientSchema, getIntrospectionQuery, printSchema } from 'graphql';
import type { TsConfigJson } from 'type-fest';

import type { GraphQLSPConfig } from './lsp';
import { hasGraphQLSP } from './lsp';
import { ensureTadaIntrospection } from './tada';

const prog = sade('gql.tada');

prog.version(process.env.npm_package_version || '0.0.0');

type GraphQLSPConfig = {
name: string;
schema: string;
tadaOutputLocation?: string;
};

function hasGraphQLSP(tsconfig: TsConfigJson): boolean {
if (!tsconfig.compilerOptions) {
console.warn('Missing compilerOptions object in tsconfig.json.');
return false;
}
async function main() {
prog
.command('generate-schema <target> <dest>')
.describe(
'Generate a GraphQL schema from a URL or introspection file, this will be generated from the parameters to this command.'
)
.option('--header', 'Pass a header to be used when fetching the introspection.')
.example("generate-schema https://example.com --header 'Authorization: Bearer token'")
.example('generate-schema ./introspection.json')
.action(async (target, destination, options) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably makes sense to read the destination from the tsconfig.json (similar to:

await ensureTadaIntrospection(foundPlugin.schema, foundPlugin.tadaOutputLocation!);
)

Both commands could later on allow for an override option (e.g. -o --output, I guess. But we have the benefit of knowing where that schema should end up

const cwd = process.cwd();
let url: URL | undefined;

if (!tsconfig.compilerOptions.plugins) {
console.warn('Missing plugins array in tsconfig.json.');
return false;
}
try {
url = new URL(target);
} catch (e) {}

const foundPlugin = tsconfig.compilerOptions.plugins.find(
(plugin) => plugin.name === '@0no-co/graphqlsp'
) as GraphQLSPConfig | undefined;
if (!foundPlugin) {
console.warn('Missing @0no-co/graphqlsp plugin in tsconfig.json.');
return false;
}
let introspection: IntrospectionQuery;
if (url) {
const headers = (Array.isArray(options.header) ? options.header : []).reduce(
(acc, item) => {
const parts = item.split(':');
return {
...acc,
[parts[0]]: parts[1],
};
},
{}
);
const response = await fetch(url!.toString(), {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: getIntrospectionQuery({
descriptions: true,
schemaDescription: false,
inputValueDeprecation: false,
directiveIsRepeatable: false,
specifiedByUrl: false,
}),
}),
});

if (!foundPlugin.schema) {
console.warn('Missing schema property in @0no-co/graphqlsp plugin in tsconfig.json.');
return false;
}
if (response.ok) {
const text = await response.text();

if (!foundPlugin.tadaOutputLocation) {
console.warn(
'Missing tadaOutputLocation property in @0no-co/graphqlsp plugin in tsconfig.json.'
);
return false;
}
try {
const result = JSON.parse(text);
if (result.data) {
introspection = (result as { data: IntrospectionQuery }).data;
} else {
console.error(`Got invalid response ${JSON.stringify(result)}`);
return;
}
} catch (e) {
console.error(`Got invalid JSON ${text}`);
return;
}
} else {
console.error(`Got invalid response ${await response.text()}`);
return;
}
} else {
const path = resolve(cwd, target);
const fileContents = await fs.readFile(path, 'utf-8');

return true;
}
try {
introspection = JSON.parse(fileContents);
} catch (e) {
console.error(`Got invalid JSON ${fileContents}`);
return;
}
}

async function main() {
prog
const schema = buildClientSchema(introspection!);
await fs.writeFile(resolve(cwd, destination), printSchema(schema), 'utf-8');
})
.command('generate-output')
.describe(
'Generate the gql.tada types file, this will look for your "tsconfig.json" and use the "@0no-co/graphqlsp" configuration to generate the file.'
Expand Down
41 changes: 41 additions & 0 deletions packages/cli-utils/src/lsp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { TsConfigJson } from 'type-fest';

export type GraphQLSPConfig = {
name: string;
schema: string;
tadaOutputLocation?: string;
};

export function hasGraphQLSP(tsconfig: TsConfigJson): boolean {
if (!tsconfig.compilerOptions) {
console.warn('Missing compilerOptions object in tsconfig.json.');
return false;
}

if (!tsconfig.compilerOptions.plugins) {
console.warn('Missing plugins array in tsconfig.json.');
return false;
}

const foundPlugin = tsconfig.compilerOptions.plugins.find(
(plugin) => plugin.name === '@0no-co/graphqlsp'
) as GraphQLSPConfig | undefined;
if (!foundPlugin) {
console.warn('Missing @0no-co/graphqlsp plugin in tsconfig.json.');
return false;
}

if (!foundPlugin.schema) {
console.warn('Missing schema property in @0no-co/graphqlsp plugin in tsconfig.json.');
return false;
}

if (!foundPlugin.tadaOutputLocation) {
console.warn(
'Missing tadaOutputLocation property in @0no-co/graphqlsp plugin in tsconfig.json.'
);
return false;
}

return true;
}
Loading