UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

322 lines • 12.5 kB
"use strict"; 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