@atomist/automation-client
Version:
Atomist API for software low-level client
322 lines • 12.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const appRoot = require("app-root-path");
const findUp = require("find-up");
const fs = require("fs");
const graphql_1 = require("graphql");
const graphql_tag_1 = require("graphql-tag");
const p = require("path");
const logger_1 = require("../../util/logger");
const string_1 = require("../util/string");
graphql_tag_1.disableFragmentWarnings();
// tslint:disable-next-line:no-var-requires
const schema = require("../../graph/schema.json");
const OperationParameterExpression = /(?:subscription|query)[\s]*([\S]*?)\s*(\([\S\s]*?\))\s*[\S\s]*?{/i;
const OperationNameExpression = /(subscription|query)[\s]*([^({\s]*)/i;
const FragmentExpression = /\.\.\.\s*([_A-Za-z][_0-9A-Za-z]*)/gi;
class ParameterEnum {
constructor(value) {
this.value = value;
}
}
exports.ParameterEnum = ParameterEnum;
function enumValue(value) {
return new ParameterEnum(value);
}
exports.enumValue = enumValue;
/**
* see src/graph/graphQL.ts
*/
function subscription(options) {
let s = options.subscription;
const fragmentDir = options.fragmentDir;
const path = options.path;
const name = options.name;
const pathToCallingFunction = options.moduleDir;
// If subscription isn't defined attempt to load from file
if (!s) {
s = locateAndLoadGraphql({ path, name }, "subscription", pathToCallingFunction);
}
// Replace variables
s = replaceParameters(s, options.variables);
// Inline fragments
s = inlineFragments(s, name, pathToCallingFunction, fragmentDir);
if (options.operationName) {
s = replaceOperationName(s, options.operationName);
}
// Inline entire subscription
if (options.inline !== false) {
s = inlineQuery(s);
}
return s;
}
exports.subscription = subscription;
/**
* Prepare a GraphQL query string for the use with Apollo.
*
* Queries can be provided by the following options:
*
* * query: string containing the subscription GraphQL, or
* * path: absolute or relative path to a .graphql file to load; if provided a relative
* path this will resolve the relative path to an absolute given the location
* of the calling script.
* * name: name of the .graphql file to load; this will walk up the directory structure
* starting at the location of the calling script and look for a folder called
* 'graphql'. Once that folder is found, by convention name is being looked for
* in the 'query' sub directory.
* * fragmentsDir: location of fragment .graphql files
* * moduleDir: location of the calling script
* * inline: remove any unneeded whitespace and line breaks from returned GraphQL string
*
* @param {{query?: string; path?: string; name?: string; fragmentDir?: string; moduleDir: string; inline?: boolean}} options
* @returns {string}
*/
function query(options) {
let q = options.query;
const fragmentDir = options.fragmentDir;
const path = options.path;
const name = options.name;
// If query isn't defined attempt to load from file
if (!q) {
q = locateAndLoadGraphql({ path, name }, "query", options.moduleDir);
}
// Inline fragments
q = inlineFragments(q, name, options.moduleDir, fragmentDir);
// Inline entire query
if (options.inline === true) {
q = inlineQuery(q);
}
return q;
}
exports.query = query;
/**
* Prepare a GraphQL mutation string for the use with Apollo.
*
* Mutations can be provided by the following options:
*
* * mutation: string containing the subscription GraphQL, or
* * path: absolute or relative path to a .graphql file to load; if provided a relative
* path this will resolve the relative path to an absolute given the location
* of the calling script.
* * name: name of the .graphql file to load; this will walk up the directory structure
* starting a t the location of the calling script and look for a folder called
* 'graphql'. Once that folder is found, by convention name is being looked for
* in the 'mutation' sub directory.
* * moduleDir: location of the calling script
* * inline: remove any unneeded whitespace and line breaks from returned GraphQL string
*
* @param {{mutation?: string; path?: string; name?: string; moduleDir: string; inline?: boolean}} options
* @returns {string}
*/
function mutate(options) {
let m = options.mutation;
const path = options.path;
const name = options.name;
// If mutation isn't defined attempt to load from file
if (!m) {
m = locateAndLoadGraphql({ path, name }, "mutation", options.moduleDir);
}
// Inline entire mutation
if (options.inline === true) {
m = inlineQuery(m);
}
return m;
}
exports.mutate = mutate;
/**
* see src/graph/graphQL.ts
*/
function ingester(options) {
const path = options.path;
const name = options.name;
const pathToCallingFunction = options.moduleDir;
return locateAndLoadGraphql({ path, name }, "ingester", pathToCallingFunction);
}
exports.ingester = ingester;
/**
* Extract operationName from the provided query or subscription
* @param {string} q
* @returns {string}
*/
function operationName(q) {
const graphql = graphql_1.parse(q);
// TODO add some validation here
return graphql.definitions[0].name.value;
}
exports.operationName = operationName;
/**
* Inline the given query. Mainly useful for nicer log messages
* @param {string} query
* @returns {string}
*/
function inlineQuery(q) {
return q.replace(/[\n\r]/g, "").replace(/\s\s+/g, " ");
}
exports.inlineQuery = inlineQuery;
/**
* Replace the operation name in the query or subscription
*/
function replaceOperationName(q, name) {
return q.replace(OperationNameExpression, `$1 ${name}`);
}
exports.replaceOperationName = replaceOperationName;
function prettyPrintErrors(errors, q) {
return errors.map(e => {
let msg = `${e.message} ${e.locations.map(l => `[${l.line},${l.column}]`).join(", ")}`;
if (q) {
for (let i = 0; i < e.positions.length; i++) {
msg += `\n${string_1.findLine(q, e.positions[i])}`;
msg += `\n${Array(e.locations[i].column).join("-")}^`;
}
}
return msg;
}).join("\n\n");
}
exports.prettyPrintErrors = prettyPrintErrors;
function replaceParameters(q, parameters = {}) {
if (Object.keys(parameters).length > 0) {
const exp = OperationParameterExpression;
if (exp.test(q)) {
const result = exp.exec(q);
// First delete the parameter declaration at the top of the subscription
q = q.replace(result[2], "");
for (const key in parameters) {
if (parameters.hasOwnProperty(key)) {
const value = parameters[key];
if (!value) {
throw new Error(`The value of variable '${key}' is undefined`);
}
// If value is defined it is a enum value
if (value.value) {
if (Array.isArray(value.value)) {
q = replace(q, `\\$${key}`, `[${value.value.join(", ")}]`);
}
else {
q = replace(q, `\\$${key}`, value.value);
}
}
else {
q = replace(q, `\\$${key}`, JSON.stringify(value));
}
}
}
// Calulate hash to suffix the subscriptionName
const hash = string_1.generateHash(q);
q = replaceOperationName(q, `${result[1]}_${hash}`);
}
}
return q;
}
exports.replaceParameters = replaceParameters;
function replace(q, key, value) {
return q.replace(new RegExp(`${key}\\b`, "g"), value);
}
function inlineFragments(q, name, moduleDir, fragmentDir) {
if (!fragmentDir && !name) {
fragmentDir = p.dirname(moduleDir);
}
else if (!fragmentDir && name) {
fragmentDir = p.resolve(findUp.sync("graphql", {
cwd: p.resolve(p.dirname(moduleDir)),
type: "directory",
}), "fragment");
}
else if (!p.isAbsolute(fragmentDir)) {
fragmentDir = p.resolve(p.dirname(moduleDir), fragmentDir);
}
if (FragmentExpression.test(q)) {
// Load all fragments
const fragments = fs.readdirSync(fragmentDir).filter(f => f.endsWith(".graphql")).map(f => {
const content = fs.readFileSync(p.join(fragmentDir, f)).toString();
const graphql = graphql_tag_1.default(content);
return {
name: (graphql.definitions[0]).name.value,
kind: (graphql.definitions[0]).kind,
body: content.slice(content.indexOf("{") + 1, content.lastIndexOf("}") - 1),
};
}).filter(f => f.kind === "FragmentDefinition");
FragmentExpression.lastIndex = 0;
let result;
// tslint:disable-next-line:no-conditional-assignment
while (result = FragmentExpression.exec(q)) {
const fragment = fragments.find(f => f.name === result[1]);
if (fragment) {
q = replace(q, result[0], fragment.body);
}
else {
throw new Error(`Fragment '${result[1]}' can't be found in '${fragmentDir}'`);
}
}
}
return q;
}
function locateAndLoadGraphql(options, subfolder, moduleDir) {
let path = options.path;
const name = options.name;
// Read subscription from file if given
if (options.path) {
if (!path.endsWith(".graphql")) {
path = `${path}.graphql`;
}
if (!p.isAbsolute(path)) {
path = p.resolve(p.dirname(moduleDir), path);
}
}
else if (options.name) {
const cwd = p.resolve(p.dirname(moduleDir));
const graphqlDir = findUp.sync("graphql", { cwd, type: "directory" });
if (graphqlDir) {
const queryDir = p.join(graphqlDir, subfolder);
const queries = fs.readdirSync(queryDir).filter(f => f.endsWith(".graphql")).filter(f => {
const content = fs.readFileSync(p.join(queryDir, f)).toString();
const graphql = graphql_tag_1.default(content);
return (graphql.definitions[0]).name.value === options.name;
});
if (queries.length === 1) {
path = p.join(queryDir, queries[0]);
}
else if (queries.length === 0) {
// Remove for next major release
logger_1.logger.warn(`No ${subfolder} graphql operation found for name '${options.name}'. ` +
`Falling back to file name lookup. Support for file name lookup will be removed in a future release.`);
if (!options.name.endsWith(".graphql")) {
path = p.join(queryDir, `${options.name}.graphql`);
}
else {
path = p.join(queryDir, options.name);
}
// End of remove for next major release
// throw new Error(`No matching ${subfolder} graphql operation found for name '${options.name}'`);
}
else {
throw new Error(`More then 1 matching ${subfolder} graphql operation found for name '${options.name}'`);
}
}
else {
throw new Error(`No graphql folder found anywhere above directory '${cwd}'. Consider specifying a path`);
}
}
else {
throw new Error("No name or path specified");
}
if (fs.existsSync(path)) {
return fs.readFileSync(path).toString();
}
else {
throw new Error(`GraphQL file '${path}' does not exist`);
}
}
function resolveAndReadFileSync(path, current = appRoot.path, parameters = {}) {
if (!path.endsWith(".graphql")) {
path = `${path}.graphql`;
}
const absolutePath = p.resolve(current, path);
if (fs.existsSync(absolutePath)) {
return replaceParameters(fs.readFileSync(absolutePath).toString(), parameters);
}
else {
throw new Error(`GraphQL file '${absolutePath}' does not exist`);
}
}
exports.resolveAndReadFileSync = resolveAndReadFileSync;
//# sourceMappingURL=graphQL.js.map