mirror of
https://github.com/usebruno/bruno.git
synced 2025-01-03 20:49:18 +01:00
feat: cli runner can now run a single request
This commit is contained in:
parent
404a516fef
commit
1c869013c6
@ -13,6 +13,7 @@
|
|||||||
"@usebruno/js": "0.1.0",
|
"@usebruno/js": "0.1.0",
|
||||||
"@usebruno/lang": "0.1.0",
|
"@usebruno/lang": "0.1.0",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
|
"fs-extra": "^10.1.0",
|
||||||
"inquirer": "^9.1.4",
|
"inquirer": "^9.1.4",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,44 @@
|
|||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const {
|
||||||
|
exists,
|
||||||
|
isFile,
|
||||||
|
isDirectory
|
||||||
|
} = require('../utils/filesystem');
|
||||||
|
const {
|
||||||
|
runSingleRequest
|
||||||
|
} = require('../runner/run-single-request');
|
||||||
const {
|
const {
|
||||||
CLI_EPILOGUE,
|
CLI_EPILOGUE,
|
||||||
} = require('../constants');
|
} = require('../constants');
|
||||||
|
|
||||||
const command = 'run';
|
const command = 'run <filename>';
|
||||||
const desc = 'Run a request';
|
const desc = 'Run a request';
|
||||||
|
|
||||||
const cmdArgs = {
|
|
||||||
filename: {
|
|
||||||
desc: 'Run a request',
|
|
||||||
type: 'string',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const builder = async (yargs) => {
|
const builder = async (yargs) => {
|
||||||
yargs.options(cmdArgs).epilogue(CLI_EPILOGUE).help();
|
yargs.example('$0 run request.bru', 'Run a request');
|
||||||
yargs.example('$0 filename', 'Run a request');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handler = async function (argv) {
|
const handler = async function (argv) {
|
||||||
try {
|
try {
|
||||||
if (!argv.filename) {
|
const { filename } = argv;
|
||||||
console.log(chalk.cyan('Please specify a filename'));
|
|
||||||
console.log(`Example: ${argv.$0} run request.bru`);
|
|
||||||
|
|
||||||
return;
|
const pathExists = await exists(filename);
|
||||||
|
if(!pathExists) {
|
||||||
|
console.error(chalk.red(`File or directory ${filename} does not exist`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _isFile = await isFile(filename);
|
||||||
|
if(_isFile) {
|
||||||
|
runSingleRequest(filename);
|
||||||
}
|
}
|
||||||
console.log("here");
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
command,
|
command,
|
||||||
desc,
|
desc,
|
||||||
builder,
|
builder,
|
||||||
cmdArgs,
|
|
||||||
handler
|
handler
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ const chalk = require('chalk');
|
|||||||
|
|
||||||
const { CLI_EPILOGUE, CLI_VERSION } = require('./constants');
|
const { CLI_EPILOGUE, CLI_VERSION } = require('./constants');
|
||||||
|
|
||||||
|
|
||||||
const printBanner = () => {
|
const printBanner = () => {
|
||||||
console.log(chalk.yellow(`Bru CLI ${CLI_VERSION}`));
|
console.log(chalk.yellow(`Bru CLI ${CLI_VERSION}`));
|
||||||
}
|
}
|
||||||
|
55
packages/bruno-cli/src/runner/bru/index.js
Normal file
55
packages/bruno-cli/src/runner/bru/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const {
|
||||||
|
bruToJsonV2
|
||||||
|
} = require('@usebruno/lang');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transformer function for converting a BRU file to JSON.
|
||||||
|
*
|
||||||
|
* We map the json response from the bru lang and transform it into the DSL
|
||||||
|
* format that is used by the bruno app
|
||||||
|
*
|
||||||
|
* @param {string} bru The BRU file content.
|
||||||
|
* @returns {object} The JSON representation of the BRU file.
|
||||||
|
*/
|
||||||
|
const bruToJson = (bru) => {
|
||||||
|
try {
|
||||||
|
const json = bruToJsonV2(bru);
|
||||||
|
|
||||||
|
let requestType = _.get(json, "meta.type");
|
||||||
|
if(requestType === "http") {
|
||||||
|
requestType = "http-request"
|
||||||
|
} else if(requestType === "graphql") {
|
||||||
|
requestType = "graphql-request";
|
||||||
|
} else {
|
||||||
|
requestType = "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
const sequence = _.get(json, "meta.seq")
|
||||||
|
|
||||||
|
const transformedJson = {
|
||||||
|
"type": requestType,
|
||||||
|
"name": _.get(json, "meta.name"),
|
||||||
|
"seq": !isNaN(sequence) ? Number(sequence) : 1,
|
||||||
|
"request": {
|
||||||
|
"method": _.upperCase(_.get(json, "http.method")),
|
||||||
|
"url": _.get(json, "http.url"),
|
||||||
|
"params": _.get(json, "query", []),
|
||||||
|
"headers": _.get(json, "headers", []),
|
||||||
|
"body": _.get(json, "body", {}),
|
||||||
|
},
|
||||||
|
"script": _.get(json, "script", ""),
|
||||||
|
"test": _.get(json, "test", "")
|
||||||
|
};
|
||||||
|
|
||||||
|
transformedJson.request.body.mode = _.get(json, "http.mode", "none");
|
||||||
|
|
||||||
|
return transformedJson;
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bruToJson
|
||||||
|
};
|
54
packages/bruno-cli/src/runner/interpolate-vars.js
Normal file
54
packages/bruno-cli/src/runner/interpolate-vars.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const Mustache = require('mustache');
|
||||||
|
const { each, forOwn } = require('lodash');
|
||||||
|
|
||||||
|
// override the default escape function to prevent escaping
|
||||||
|
Mustache.escape = function (value) {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const interpolateVars = (request, envVars = {}, collectionVariables ={}) => {
|
||||||
|
const interpolate = (str) => {
|
||||||
|
if(!str || !str.length || typeof str !== "string") {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectionVariables take precedence over envVars
|
||||||
|
const combinedVars = {
|
||||||
|
...envVars,
|
||||||
|
...collectionVariables
|
||||||
|
};
|
||||||
|
|
||||||
|
return Mustache.render(str, combinedVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.url = interpolate(request.url);
|
||||||
|
|
||||||
|
forOwn(request.headers, (value, key) => {
|
||||||
|
request.headers[key] = interpolate(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(request.headers["content-type"] === "application/json") {
|
||||||
|
if(typeof request.data === "object") {
|
||||||
|
try {
|
||||||
|
let parsed = JSON.stringify(request.data);
|
||||||
|
parsed = interpolate(parsed);
|
||||||
|
request.data = JSON.parse(parsed);
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof request.data === "string") {
|
||||||
|
if(request.data.length) {
|
||||||
|
request.data = interpolate(request.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
each(request.params, (param) => {
|
||||||
|
param.value = interpolate(param.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = interpolateVars;
|
71
packages/bruno-cli/src/runner/prepare-request.js
Normal file
71
packages/bruno-cli/src/runner/prepare-request.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const { get, each, filter } = require('lodash');
|
||||||
|
const qs = require('qs');
|
||||||
|
|
||||||
|
const prepareRequest = (request) => {
|
||||||
|
const headers = {};
|
||||||
|
each(request.headers, (h) => {
|
||||||
|
if (h.enabled) {
|
||||||
|
headers[h.name] = h.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let axiosRequest = {
|
||||||
|
method: request.method,
|
||||||
|
url: request.url,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
|
request.body = request.body || {};
|
||||||
|
|
||||||
|
if (request.body.mode === 'json') {
|
||||||
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
|
try {
|
||||||
|
axiosRequest.data = JSON.parse(request.body.json);
|
||||||
|
} catch (ex) {
|
||||||
|
axiosRequest.data = request.body.json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'text') {
|
||||||
|
axiosRequest.headers['content-type'] = 'text/plain';
|
||||||
|
axiosRequest.data = request.body.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'xml') {
|
||||||
|
axiosRequest.headers['content-type'] = 'text/xml';
|
||||||
|
axiosRequest.data = request.body.xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'formUrlEncoded') {
|
||||||
|
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||||
|
const params = {};
|
||||||
|
const enabledParams = filter(request.body.formUrlEncoded, (p) => p.enabled);
|
||||||
|
each(enabledParams, (p) => (params[p.name] = p.value));
|
||||||
|
axiosRequest.data = qs.stringify(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'multipartForm') {
|
||||||
|
const params = {};
|
||||||
|
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
|
||||||
|
each(enabledParams, (p) => (params[p.name] = p.value));
|
||||||
|
axiosRequest.headers['content-type'] = 'multipart/form-data';
|
||||||
|
axiosRequest.data = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'graphql') {
|
||||||
|
const graphqlQuery = {
|
||||||
|
query: get(request, 'body.graphql.query'),
|
||||||
|
variables: JSON.parse(get(request, 'body.graphql.variables') || '{}')
|
||||||
|
};
|
||||||
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
|
axiosRequest.data = graphqlQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.script && request.script.length) {
|
||||||
|
axiosRequest.script = request.script;
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = prepareRequest;
|
85
packages/bruno-cli/src/runner/run-single-request.js
Normal file
85
packages/bruno-cli/src/runner/run-single-request.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
const Mustache = require('mustache');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { forOwn, each, extend, get } = require('lodash');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const axios = require('axios');
|
||||||
|
const prepareRequest = require('./prepare-request');
|
||||||
|
const { ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
|
const {
|
||||||
|
bruToJson
|
||||||
|
} = require('./bru');
|
||||||
|
|
||||||
|
// override the default escape function to prevent escaping
|
||||||
|
Mustache.escape = function (value) {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnvVars = (environment = {}) => {
|
||||||
|
const variables = environment.variables;
|
||||||
|
if (!variables || !variables.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const envVars = {};
|
||||||
|
each(variables, (variable) => {
|
||||||
|
if(variable.enabled) {
|
||||||
|
envVars[variable.name] = Mustache.escape(variable.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return envVars;
|
||||||
|
};
|
||||||
|
|
||||||
|
const runSingleRequest = async function (filepath) {
|
||||||
|
try {
|
||||||
|
const bruContent = fs.readFileSync(filepath, 'utf8');
|
||||||
|
|
||||||
|
const bruJson = bruToJson(bruContent);
|
||||||
|
const request = prepareRequest(bruJson.request);
|
||||||
|
|
||||||
|
// make axios work in node using form data
|
||||||
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
|
if(request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
||||||
|
const form = new FormData();
|
||||||
|
forOwn(request.data, (value, key) => {
|
||||||
|
form.append(key, value);
|
||||||
|
});
|
||||||
|
extend(request.headers, form.getHeaders());
|
||||||
|
request.data = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envVars = getEnvVars({});
|
||||||
|
|
||||||
|
//todo:
|
||||||
|
const collectionVariables = {};
|
||||||
|
const collectionPath = '/Users/anoop/Github/github-rest-api-collection';
|
||||||
|
|
||||||
|
if(request.script && request.script.length) {
|
||||||
|
let script = request.script + '\n if (typeof onRequest === "function") {onRequest(__brunoRequest);}';
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = scriptRuntime.runRequestScript(script, request, envVars, collectionVariables, collectionPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios(request);
|
||||||
|
|
||||||
|
if(request.script && request.script.length) {
|
||||||
|
let script = request.script + '\n if (typeof onResponse === "function") {onResponse(__brunoResponse);}';
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = scriptRuntime.runResponseScript(script, response, envVars, collectionVariables, collectionPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFile = get(bruJson, 'request.tests');
|
||||||
|
if(testFile && testFile.length) {
|
||||||
|
const testRuntime = new TestRuntime();
|
||||||
|
const result = testRuntime.runTests(testFile, request, response, envVars, collectionVariables, collectionPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(response.status);
|
||||||
|
} catch (err) {
|
||||||
|
Promise.reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
runSingleRequest
|
||||||
|
};
|
114
packages/bruno-cli/src/utils/filesystem.js
Normal file
114
packages/bruno-cli/src/utils/filesystem.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const fsPromises = require('fs/promises');
|
||||||
|
|
||||||
|
const exists = async p => {
|
||||||
|
try {
|
||||||
|
await fsPromises.access(p);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSymbolicLink = filepath => {
|
||||||
|
try {
|
||||||
|
return fs.existsSync(filepath) && fs.lstatSync(filepath).isSymbolicLink();
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFile = filepath => {
|
||||||
|
try {
|
||||||
|
return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile();
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDirectory = dirPath => {
|
||||||
|
try {
|
||||||
|
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeAndResolvePath = pathname => {
|
||||||
|
if (isSymbolicLink(pathname)) {
|
||||||
|
const absPath = path.dirname(pathname);
|
||||||
|
const targetPath = path.resolve(absPath, fs.readlinkSync(pathname));
|
||||||
|
if (isFile(targetPath) || isDirectory(targetPath)) {
|
||||||
|
return path.resolve(targetPath);
|
||||||
|
}
|
||||||
|
console.error(`Cannot resolve link target "${pathname}" (${targetPath}).`)
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return path.resolve(pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeFile = async (pathname, content) => {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(pathname, content, {
|
||||||
|
encoding: "utf8"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasJsonExtension = filename => {
|
||||||
|
if (!filename || typeof filename !== 'string') return false
|
||||||
|
return ['json'].some(ext => filename.toLowerCase().endsWith(`.${ext}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasBruExtension = filename => {
|
||||||
|
if (!filename || typeof filename !== 'string') return false
|
||||||
|
return ['bru'].some(ext => filename.toLowerCase().endsWith(`.${ext}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const createDirectory = async (dir) => {
|
||||||
|
if(!dir) {
|
||||||
|
throw new Error(`directory: path is null`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(dir)){
|
||||||
|
throw new Error(`directory: ${dir} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.mkdirSync(dir);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchForFiles = (dir, extension) => {
|
||||||
|
let results = [];
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(dir, file);
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
results = results.concat(searchForFiles(filePath, extension));
|
||||||
|
} else if (path.extname(file) === extension) {
|
||||||
|
results.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchForBruFiles = (dir) => {
|
||||||
|
return searchForFiles(dir, '.bru');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
exists,
|
||||||
|
isSymbolicLink,
|
||||||
|
isFile,
|
||||||
|
isDirectory,
|
||||||
|
normalizeAndResolvePath,
|
||||||
|
writeFile,
|
||||||
|
hasJsonExtension,
|
||||||
|
hasBruExtension,
|
||||||
|
createDirectory,
|
||||||
|
searchForFiles,
|
||||||
|
searchForBruFiles
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user