mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-22 14:41:04 +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/lang": "0.1.0",
|
||||
"chalk": "^3.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"inquirer": "^9.1.4",
|
||||
"yargs": "^17.6.2"
|
||||
}
|
||||
|
@ -1,43 +1,44 @@
|
||||
const chalk = require('chalk');
|
||||
const {
|
||||
exists,
|
||||
isFile,
|
||||
isDirectory
|
||||
} = require('../utils/filesystem');
|
||||
const {
|
||||
runSingleRequest
|
||||
} = require('../runner/run-single-request');
|
||||
const {
|
||||
CLI_EPILOGUE,
|
||||
} = require('../constants');
|
||||
|
||||
const command = 'run';
|
||||
const command = 'run <filename>';
|
||||
const desc = 'Run a request';
|
||||
|
||||
const cmdArgs = {
|
||||
filename: {
|
||||
desc: 'Run a request',
|
||||
type: 'string',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const builder = async (yargs) => {
|
||||
yargs.options(cmdArgs).epilogue(CLI_EPILOGUE).help();
|
||||
yargs.example('$0 filename', 'Run a request');
|
||||
yargs.example('$0 run request.bru', 'Run a request');
|
||||
};
|
||||
|
||||
const handler = async function (argv) {
|
||||
try {
|
||||
if (!argv.filename) {
|
||||
console.log(chalk.cyan('Please specify a filename'));
|
||||
console.log(`Example: ${argv.$0} run request.bru`);
|
||||
const { filename } = argv;
|
||||
|
||||
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) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
command,
|
||||
desc,
|
||||
builder,
|
||||
cmdArgs,
|
||||
handler
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ const chalk = require('chalk');
|
||||
|
||||
const { CLI_EPILOGUE, CLI_VERSION } = require('./constants');
|
||||
|
||||
|
||||
const printBanner = () => {
|
||||
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