@graphql-inspector/action
Version:
GraphQL Inspector functionality for GitHub Actions
164 lines (163 loc) • 5.44 kB
JavaScript
import Dataloader from 'dataloader';
import { buildClientSchema, getIntrospectionQuery, printSchema, Source } from 'graphql';
import yaml from 'js-yaml';
import { fetch } from '@whatwg-node/fetch';
import { isNil, objectFromEntries, parseEndpoint } from './utils.js';
function createGetFilesQuery(variableMap) {
const variables = Object.keys(variableMap)
.map(name => `$${name}: String!`)
.join(', ');
const files = Object.keys(variableMap)
.map(name => {
return `
${name}: object(expression: $${name}) {
... on Blob {
text
}
}
`;
})
.join('\n');
return /* GraphQL */ `
query GetFile($repo: String!, $owner: String!, ${variables}) {
repository(name: $repo, owner: $owner) {
${files}
}
}
`.replace(/\s+/g, ' ');
}
export function createFileLoader(config) {
const loader = new Dataloader(async (inputs) => {
const variablesMap = objectFromEntries(inputs.map(input => [input.alias, `${input.ref}:${input.path}`]));
const { context, repo, owner } = config;
const result = await context.octokit.graphql(createGetFilesQuery(variablesMap), {
repo,
owner,
...variablesMap,
});
return Promise.all(inputs.map(async (input) => {
const alias = input.alias;
try {
if (!result) {
throw new Error(`No result :(`);
}
if (result.data) {
return result.data.repository[alias].text;
}
return result.repository[alias].text;
}
catch (error) {
const failure = new Error(`Failed to load '${input.path}' (ref: ${input.ref})`);
if (input.throwNotFound === false) {
if (input.onError) {
input.onError(failure);
}
else {
console.error(failure);
}
return null;
}
throw failure;
}
}));
}, {
batch: true,
maxBatchSize: 5,
cacheKeyFn(obj) {
return `${obj.ref} - ${obj.path}`;
},
});
return input => loader.load(input);
}
export function createConfigLoader(config, loadFile) {
const loader = new Dataloader(ids => {
const errors = [];
const onError = (error) => {
errors.push(error);
};
return Promise.all(ids.map(async (id) => {
const [yamlConfig, ymlConfig, pkgFile] = await Promise.all([
loadFile({
...config,
alias: 'yaml',
path: `.github/${id}.yaml`,
throwNotFound: false,
onError,
}),
loadFile({
...config,
alias: 'yml',
path: `.github/${id}.yml`,
throwNotFound: false,
onError,
}),
loadFile({
...config,
alias: 'pkg',
path: 'package.json',
throwNotFound: false,
onError,
}),
]);
if (yamlConfig || ymlConfig) {
return yaml.load((yamlConfig || ymlConfig));
}
if (pkgFile) {
try {
const pkg = JSON.parse(pkgFile);
if (pkg[id]) {
return pkg[id];
}
}
catch (error) {
errors.push(error);
}
}
console.error([`Failed to load config:`, ...errors].join('\n'));
return null;
}));
}, {
batch: false,
});
return () => loader.load('graphql-inspector');
}
export async function printSchemaFromEndpoint(endpoint) {
const config = parseEndpoint(endpoint);
const response = await fetch(config.url, {
method: config.method,
headers: config.headers,
body: JSON.stringify({
query: getIntrospectionQuery().replace(/\s+/g, ' ').trim(),
}),
});
const { data } = await response.json();
const introspection = data;
return printSchema(buildClientSchema(introspection, {
assumeValid: true,
}));
}
export async function loadSources({ config, oldPointer, newPointer, loadFile, }) {
// Here, config.endpoint is defined only if target's branch matches branch of environment
// otherwise it's empty
const useEndpoint = !isNil(config.endpoint);
const [oldFile, newFile] = await Promise.all([
useEndpoint
? printSchemaFromEndpoint(config.endpoint)
: loadFile({
...oldPointer,
alias: 'oldSource',
}),
loadFile({
...newPointer,
alias: 'newSource',
}),
]);
return {
old: new Source(oldFile, useEndpoint
? typeof config.endpoint === 'string'
? config.endpoint
: config.endpoint?.url
: `${oldPointer.ref}:${oldPointer.path}`),
new: new Source(newFile, `${newPointer.ref}:${newPointer.path}`),
};
}