Skip to content

Commit

Permalink
Use GraphQL Config
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela committed Apr 29, 2020
1 parent b1ad352 commit a723e27
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 42 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@types/express": "4.17.6",
"@types/jest": "25.2.1",
"@types/node": "13.13.4",
"@types/yamljs": "0.2.30",
"@types/yargs": "15.0.4",
"@zeit/ncc": "0.22.1",
"axios": "0.19.2",
Expand Down
31 changes: 18 additions & 13 deletions packages/ci/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,30 @@ async function main() {
.help()
.showHelpOnFail(false)
.fail((msg, error) => {
if (msg.includes('Unknown argument:')) {
const commandName = msg.replace('Unknown argument: ', '').toLowerCase();
if (msg) {
if (msg.includes('Unknown argument:')) {
const commandName = msg
.replace('Unknown argument: ', '')
.toLowerCase();

Logger.error(`Command '${commandName}' not found`);
Logger.error(`Command '${commandName}' not found`);

if (availableCommands.includes(commandName)) {
Logger.log(
` Try to install @graphql-inspector/${commandName}-command`,
);
if (availableCommands.includes(commandName)) {
Logger.log(
` Try to install @graphql-inspector/${commandName}-command`,
);
}
} else if (msg.includes('Not enough')) {
Logger.error(msg);
Logger.info('Specify --help for available options');
} else {
Logger.error(msg);
}
} else if (msg.includes('Not enough')) {
Logger.error(msg);
Logger.info('Specify --help for available options');
} else {
Logger.error(msg);
}

if (error) {
throw error;
Logger.error(error.message);
console.error(error);
}

process.exit(1);
Expand Down
1 change: 1 addition & 0 deletions packages/commands/commands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"yargs": "15.3.1"
},
"dependencies": {
"yamljs": "0.3.0",
"tslib": "^1.11.1"
},
"scripts": {
Expand Down
148 changes: 148 additions & 0 deletions packages/commands/commands/src/graphql-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {GraphQLConfig} from '@graphql-inspector/config';
import {readFile, stat} from 'fs';
import {resolve} from 'path';
import {parse as parseYAML} from 'yamljs';

const searchPlaces = [
`graphql.config.js`,
'graphql.config.json',
'graphql.config.yaml',
'graphql.config.yml',
'.graphqlrc',
'.graphqlrc.js',
'.graphqlrc.json',
'.graphqlrc.yml',
'.graphqlrc.yaml',
];

const legacySearchPlaces = [
'.graphqlconfig',
'.graphqlconfig.json',
'.graphqlconfig.yaml',
'.graphqlconfig.yml',
];

export type PickPointers = (
args: {
config?: boolean;
schema?: string;
documents?: string;
},
required: {
schema: boolean;
documents: boolean;
},
) => Promise<{
schema?: string;
documents?: string;
}>;

export const pickPointers: PickPointers = async (args, required) => {
let schema: string | undefined;
let documents: string | undefined;

if (args.config) {
const config = await useGraphQLConfig();

schema = config.schema;
documents = config.documents;
} else {
schema = args.schema;
documents = args.documents;
}

if (required.documents || required.schema) {
const errors: string[] = [];

if (required.schema && !schema) {
errors.push(`Schema pointer is missing`);
}

if (required.documents && !documents) {
errors.push(`Documents pointer is missing`);
}

if (errors.length) {
throw new Error(errors.join('\n'));
}
}

return {
schema,
documents,
};
};

async function useGraphQLConfig(): Promise<GraphQLConfig> {
const found = await firstExisting(searchPlaces);

if (found) {
return parse(found);
}

const foundLegacy = await firstExisting(legacySearchPlaces);

if (foundLegacy) {
return parse(foundLegacy);
}

throw new Error(`Couldn't find a GraphQL Config`);
}

async function firstExisting(places: string[]) {
const statuses = await Promise.all(
places.map((place) => resolve(process.cwd(), place)).map(exists),
);

return places.find((_, i) => statuses[i] === true);
}

async function parse(path: string): Promise<GraphQLConfig> {
const normalizedPath = resolve(process.cwd(), path.toLowerCase());

if (normalizedPath.endsWith('.js')) {
const mod = require(normalizedPath);

return mod.default || mod;
}

const content = await read(path);

if (normalizedPath.endsWith('.yml') || normalizedPath.endsWith('.yaml')) {
return parseYAML(content);
}

if (normalizedPath.endsWith('.json')) {
return JSON.parse(content);
}

try {
return parseYAML(content);
} catch (error) {}

try {
return JSON.parse(content);
} catch (error) {}

throw new Error(`Failed to parse: ${path}`);
}

function read(path: string): Promise<string> {
return new Promise((resolve, reject) => {
readFile(path, 'utf-8', (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}

function exists(path: string): Promise<boolean> {
return new Promise((resolve) => {
stat(path, (error) => {
resolve(error ? false : true);
});
});
}
13 changes: 11 additions & 2 deletions packages/commands/commands/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@ import {InspectorConfig} from '@graphql-inspector/config';
import {Loaders} from '@graphql-inspector/loaders';
import {isAbsolute, resolve} from 'path';
import yargs, {CommandModule} from 'yargs';
import {pickPointers, PickPointers} from './graphql-config';

export {CommandModule as Command};
export {CommandModule as Command, PickPointers};

export interface UseCommandsAPI {
config: InspectorConfig;
loaders: Loaders;
pickPointers: PickPointers;
}

export type CommandFactory<T = {}, U = {}> = (
api: UseCommandsAPI,
) => CommandModule<T, U>;

export function useCommands(api: UseCommandsAPI): CommandModule[] {
export function useCommands(
input: Omit<UseCommandsAPI, 'pickPointers'>,
): CommandModule[] {
const api = {
...input,
pickPointers,
};

return api.config.commands.map((name) => loadCommand(name)(api));
}

Expand Down
25 changes: 17 additions & 8 deletions packages/commands/coverage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,25 @@ import {writeFileSync} from 'fs';
export default createCommand<
{},
{
schema: string;
documents: string;
schema?: string;
documents?: string;
config?: boolean;
write?: string;
silent?: boolean;
} & GlobalArgs
>((api) => {
return {
command: 'coverage <documents> <schema>',
command: 'coverage [documents] [schema]',
describe: 'Schema coverage based on documents',
builder(yargs) {
return yargs
.positional('schema', {
describe: 'Point to a schema',
type: 'string',
demandOption: true,
})
.positional('documents', {
describe: 'Point to documents',
type: 'string',
demandOption: true,
})
.options({
w: {
Expand All @@ -49,19 +48,29 @@ export default createCommand<
describe: 'Do not render any stats in the terminal',
type: 'boolean',
},
c: {
alias: 'config',
describe: 'Use GraphQL Config to find schema and documents',
type: 'boolean',
},
});
},
async handler(args) {
const {loaders} = api;
const {loaders, pickPointers} = api;
const writePath = args.write;
const shouldWrite = typeof writePath !== 'undefined';
const {headers, token} = parseGlobalArgs(args);

const schema = await loaders.loadSchema(args.schema, {
const pointer = await pickPointers(args, {
documents: true,
schema: true,
});

const schema = await loaders.loadSchema(pointer.schema!, {
token,
headers,
});
const documents = await loaders.loadDocuments(args.documents);
const documents = await loaders.loadDocuments(pointer.documents!);
const coverage = calculateCoverage(
schema,
documents.map((doc) => new Source(print(doc.document!), doc.location)),
Expand Down
19 changes: 14 additions & 5 deletions packages/commands/serve/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@ import {fake} from './fake';
export default createCommand<
{},
{
schema: string;
schema?: string;
port: number;
} & GlobalArgs
>((api) => {
const {loaders} = api;
const {loaders, pickPointers} = api;

return {
command: 'serve <schema>',
command: 'serve [schema]',
describe: 'Compare two GraphQL Schemas',
builder(yargs) {
return yargs
.positional('schema', {
describe: 'Point to a schema',
type: 'string',
demandOption: true,
})
.options({
port: {
Expand All @@ -36,11 +35,21 @@ export default createCommand<
type: 'number',
default: 4000,
},
c: {
alias: 'config',
describe: 'Use GraphQL Config to find schema and documents',
type: 'boolean',
},
});
},
async handler(args) {
const {headers, token} = parseGlobalArgs(args);
const schema = await loaders.loadSchema(args.schema, {
const pointer = await pickPointers(args, {
schema: true,
documents: false,
});

const schema = await loaders.loadSchema(pointer.schema!, {
headers,
token,
});
Expand Down
Loading

0 comments on commit a723e27

Please sign in to comment.