mirror of https://github.com/Seich/Beau.git
Merge branch 'master' into next-schema-validation
This commit is contained in:
commit
0a7fbc90ff
189
bin/beau
189
bin/beau
|
|
@ -1,188 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
const program = require('commander');
|
||||
const process = require('process');
|
||||
const Beau = require('../src/beau');
|
||||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const { Line, Spinner } = require('clui');
|
||||
const clc = require('cli-color');
|
||||
const jsome = require('jsome');
|
||||
const dotenv = require('dotenv');
|
||||
const updateNotifier = require('update-notifier');
|
||||
|
||||
const package = require('../package.json');
|
||||
|
||||
updateNotifier({ pkg: package }).notify();
|
||||
|
||||
program.version(package.version);
|
||||
|
||||
program
|
||||
.command('request <alias>')
|
||||
.option(
|
||||
'-c --config <config>',
|
||||
'Specify your request config file. Defaults to beau.yml in the current directory.',
|
||||
'beau.yml'
|
||||
)
|
||||
.option(
|
||||
'--verbose',
|
||||
'Show all the information available on the current request.',
|
||||
false
|
||||
)
|
||||
.option('--no-format', 'Return the text without any special formatting.')
|
||||
.action(async (alias, { config, format, verbose }) => {
|
||||
const beau = loadConfig(config);
|
||||
let spinner;
|
||||
|
||||
if (format) {
|
||||
spinner = new Spinner(clc.yellow(`Requesting: ${alias}`));
|
||||
spinner.start();
|
||||
}
|
||||
|
||||
try {
|
||||
let res = await beau.requests.execByAlias(alias);
|
||||
let { status, headers, body } = res.response;
|
||||
let { endpoint } = res.request;
|
||||
|
||||
if (format) {
|
||||
spinner.stop();
|
||||
|
||||
status = status.toString().startsWith(2)
|
||||
? clc.green(status)
|
||||
: clc.red(status);
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column('Status', 20, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(status, 20)
|
||||
.column(endpoint)
|
||||
.output();
|
||||
|
||||
new Line().output();
|
||||
|
||||
if (verbose) {
|
||||
jsome(res);
|
||||
} else {
|
||||
jsome(body);
|
||||
}
|
||||
} else {
|
||||
console.log(status);
|
||||
console.log(endpoint);
|
||||
console.log(JSON.stringify(headers));
|
||||
console.log(JSON.stringify(body));
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
new Line().output();
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.option(
|
||||
'-c --config <config>',
|
||||
'Specify your request config file. Defaults to beau.yml in the current directory.',
|
||||
'beau.yml'
|
||||
)
|
||||
.option('--no-format', 'Return the text without any special formatting.')
|
||||
.action(({ config, format }) => {
|
||||
const beau = loadConfig(config);
|
||||
|
||||
if (format) {
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column('HTTP Verb', 20, [clc.cyan])
|
||||
.column('Alias', 30, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
beau.requests.list.forEach(({ VERB, ALIAS, ENDPOINT, PATH }) =>
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(VERB, 20, [clc.yellow])
|
||||
.column(ALIAS, 30, [clc.yellow])
|
||||
.column(ENDPOINT.replace(/\/$/, '') + '/' + PATH.replace(/^\//, ''))
|
||||
.output()
|
||||
);
|
||||
|
||||
new Line().output();
|
||||
} else {
|
||||
beau.requests.list.forEach(({ VERB, ALIAS, ENDPOINT, PATH }) => {
|
||||
console.log(`${VERB}\t${ALIAS}\t${ENDPOINT.replace(/\/$/, '')}/${PATH.replace(/^\//, '')}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.option(
|
||||
'-e --endpoint <endpoint>',
|
||||
'Allows you to set the default endpoint',
|
||||
null
|
||||
)
|
||||
.action(({ endpoint }) => {
|
||||
const newFile = `# Beau.yml
|
||||
|
||||
version: 1${
|
||||
endpoint === null
|
||||
? `
|
||||
# endpoint: http://example.com
|
||||
`
|
||||
: `
|
||||
endpoint: ${endpoint}
|
||||
`
|
||||
}
|
||||
|
||||
# defaults:
|
||||
# params:
|
||||
# userId: 25
|
||||
|
||||
|
||||
|
||||
# GET /profile: profile
|
||||
|
||||
# GET /posts:
|
||||
# alias: posts
|
||||
# params:
|
||||
# order: ASC
|
||||
|
||||
# POST /profile:
|
||||
# alias: save-profile
|
||||
# headers:
|
||||
# authentication: Bearer token
|
||||
# payload:
|
||||
# name: David
|
||||
# lastname: Diaz
|
||||
`;
|
||||
if (!fs.existsSync('beau.yml')) {
|
||||
fs.writeFileSync('beau.yml', newFile);
|
||||
console.info('beau.yml created!');
|
||||
} else {
|
||||
console.error('beau.yml already exists.');
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
if (!program.args.length) {
|
||||
program.help();
|
||||
}
|
||||
|
||||
function loadConfig(configFile) {
|
||||
if (!fs.existsSync(configFile)) {
|
||||
console.error(`The config file, ${configFile} was not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(fs.readFileSync(configFile, 'utf-8'));
|
||||
const env = dotenv.config().parsed || {};
|
||||
|
||||
return new Beau(config, env);
|
||||
}
|
||||
require('@oclif/command')
|
||||
.run()
|
||||
.catch(require('@oclif/errors/handle'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const dotenv = require('dotenv');
|
||||
const { Command, flags } = require('@oclif/command');
|
||||
|
||||
const Beau = require('../../src/beau');
|
||||
|
||||
class Base extends Command {
|
||||
loadConfig(configFile) {
|
||||
if (!fs.existsSync(configFile)) {
|
||||
this.error(`The config file, ${configFile} was not found.`);
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(fs.readFileSync(configFile, 'utf-8'));
|
||||
const env = dotenv.config().parsed || {};
|
||||
|
||||
return new Beau(config, env);
|
||||
}
|
||||
}
|
||||
|
||||
Base.flags = {
|
||||
config: flags.string({
|
||||
char: 'c',
|
||||
description: 'The configuration file to be used.',
|
||||
default: 'beau.yml'
|
||||
}),
|
||||
verbose: flags.boolean({
|
||||
char: 'V',
|
||||
description: 'Show all additional information available for a command.'
|
||||
}),
|
||||
'no-format': flags.boolean({
|
||||
description: `Disables color formatting for usage on external tools.`
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = Base;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
const clc = require('cli-color');
|
||||
const { Line } = require('clui');
|
||||
const { flags } = require('@oclif/command');
|
||||
|
||||
const Base = require('../base');
|
||||
|
||||
class ListCommand extends Base {
|
||||
async run() {
|
||||
const { flags } = this.parse(ListCommand);
|
||||
const Beau = this.loadConfig(flags.config);
|
||||
|
||||
if (flags['no-format']) {
|
||||
return Beau.requests.list.forEach(
|
||||
({ VERB, ALIAS, ENDPOINT, PATH }) =>
|
||||
this.log(
|
||||
VERB +
|
||||
`\t` +
|
||||
ALIAS +
|
||||
`\t` +
|
||||
ENDPOINT.replace(/\/$/, '') +
|
||||
`/` +
|
||||
PATH.replace(/^\//, '')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column('HTTP Verb', 20, [clc.cyan])
|
||||
.column('Alias', 30, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
Beau.requests.list.forEach(({ VERB, ALIAS, ENDPOINT, PATH }) =>
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(VERB, 20, [clc.yellow])
|
||||
.column(ALIAS, 30, [clc.yellow])
|
||||
.column(
|
||||
ENDPOINT.replace(/\/$/, '') + '/' + PATH.replace(/^\//, '')
|
||||
)
|
||||
.output()
|
||||
);
|
||||
|
||||
new Line().output();
|
||||
}
|
||||
}
|
||||
|
||||
ListCommand.description = `Lists all available requests in the config file.`;
|
||||
ListCommand.flags = { ...Base.flags };
|
||||
|
||||
module.exports = ListCommand;
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
const clc = require('cli-color');
|
||||
const jsome = require('jsome');
|
||||
const { Line, Spinner } = require('clui');
|
||||
const { flags } = require('@oclif/command');
|
||||
|
||||
const Base = require('../base');
|
||||
|
||||
class RequestCommand extends Base {
|
||||
prettyOutput(res, verbose = false) {
|
||||
let { status, body } = res.response;
|
||||
|
||||
this.spinner.stop();
|
||||
|
||||
status = status.toString().startsWith(2)
|
||||
? clc.green(status)
|
||||
: clc.red(status);
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column('Status', 20, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(status, 20)
|
||||
.column(res.request.endpoint)
|
||||
.output();
|
||||
|
||||
new Line().output();
|
||||
|
||||
jsome((verbose ? res : body) || null);
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { flags, args } = this.parse(RequestCommand);
|
||||
const Beau = this.loadConfig(flags.config);
|
||||
this.spinner = new Spinner(clc.yellow(`Requesting: ${args.alias}`), [
|
||||
'⣾',
|
||||
'⣽',
|
||||
'⣻',
|
||||
'⢿',
|
||||
'⡿',
|
||||
'⣟',
|
||||
'⣯',
|
||||
'⣷'
|
||||
]);
|
||||
|
||||
try {
|
||||
if (!flags['no-format']) {
|
||||
this.spinner.start();
|
||||
}
|
||||
|
||||
let res = await Beau.requests.execByAlias(args.alias);
|
||||
|
||||
if (flags['no-format']) {
|
||||
this.log(res.response.status);
|
||||
this.log(res.request.endpoint);
|
||||
this.log(JSON.stringify(res.response.headers));
|
||||
this.log(JSON.stringify(res.response.body));
|
||||
} else {
|
||||
this.prettyOutput(res, flags.verbose);
|
||||
}
|
||||
} catch (err) {
|
||||
new Line().output();
|
||||
this.spinner.stop();
|
||||
this.error(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RequestCommand.description = `Executes a request by name.`;
|
||||
RequestCommand.flags = { ...Base.flags };
|
||||
|
||||
RequestCommand.args = [
|
||||
{
|
||||
name: 'alias',
|
||||
required: true,
|
||||
description: `The alias of the request to execute.`
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = RequestCommand;
|
||||
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
|
|
@ -11,23 +11,32 @@
|
|||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.4.16",
|
||||
"@oclif/config": "^1.6.13",
|
||||
"@oclif/plugin-help": "^1.2.5",
|
||||
"@oclif/plugin-warn-if-update-available": "^1.3.6",
|
||||
"cli-color": "^1.1.0",
|
||||
"clui": "^0.3.1",
|
||||
"commander": "^2.15.1",
|
||||
"deepmerge": "^2.1.0",
|
||||
"dotenv": "^5.0.1",
|
||||
"joi": "^13.2.0",
|
||||
"globby": "^8.0.1",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"js-yaml": "^3.11.0",
|
||||
"jsome": "^2.5.0",
|
||||
"request": "^2.85.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"requireg": "^0.1.6",
|
||||
"update-notifier": "^2.5.0"
|
||||
"requireg": "^0.1.6"
|
||||
},
|
||||
"repository": "git@github.com:Seich/Beau.git",
|
||||
"devDependencies": {
|
||||
"jest": "^22.4.0"
|
||||
},
|
||||
"oclif": {
|
||||
"commands": "./bin/cli/commands",
|
||||
"bin": "beau",
|
||||
"plugins": ["@oclif/plugin-help", "@oclif/plugin-warn-if-update-available"]
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"notify": true
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ Beau {
|
|||
"DEPENDENCIES": Set {},
|
||||
"ENDPOINT": "http://jsonplaceholder.typicode.com",
|
||||
"FORM": undefined,
|
||||
"FORMDATA": undefined,
|
||||
"HEADERS": Object {
|
||||
"authentication": "hello",
|
||||
},
|
||||
|
|
@ -218,6 +219,7 @@ Beau {
|
|||
"DEPENDENCIES": Set {},
|
||||
"ENDPOINT": "http://jsonplaceholder.typicode.com",
|
||||
"FORM": undefined,
|
||||
"FORMDATA": undefined,
|
||||
"HEADERS": Object {
|
||||
"authentication": "hello",
|
||||
"hello": "world",
|
||||
|
|
|
|||
|
|
@ -1,24 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Beau's plugin system Dynamic Values should look for dynamic values executing and replacing them 1`] = `
|
||||
Object {
|
||||
"body": "Hello World",
|
||||
"request": Object {
|
||||
"body": undefined,
|
||||
"endpoint": "http://example.com/hello/3",
|
||||
"headers": Object {
|
||||
"count": "3",
|
||||
"preRequestModifier": true,
|
||||
},
|
||||
},
|
||||
"response": Object {
|
||||
"body": "Hello World",
|
||||
"headers": Array [],
|
||||
"status": 200,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Beau's plugin system Request Modifiers should modify the request and response using modifiers. 1`] = `
|
||||
Object {
|
||||
"body": "Hello World",
|
||||
|
|
|
|||
|
|
@ -65,21 +65,31 @@ describe(`Beau's plugin system`, () => {
|
|||
endpoint: 'http://example.com',
|
||||
alias: 'say-hello',
|
||||
headers: {
|
||||
count: '$[add(1, $value2)]'
|
||||
}
|
||||
count: '$[add(1, $value2)]',
|
||||
empty: ''
|
||||
},
|
||||
payload: 'counted $[add(1, $value2)] so far.'
|
||||
},
|
||||
plugins
|
||||
);
|
||||
});
|
||||
|
||||
it(`should look for dynamic values executing and replacing them`, async () => {
|
||||
let cache = new RequestCache();
|
||||
cache.add('value2', '2');
|
||||
|
||||
it(`should look for dynamic values executing and replacing them`, async () => {
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.body', 'counted 3 so far.');
|
||||
});
|
||||
|
||||
expect(req).toHaveProperty('request.headers.count', '3');
|
||||
expect(req).toMatchSnapshot();
|
||||
it(`should change the internal datatype if the only thing in the value is the dynamic value`, async () => {
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.headers.count', 3);
|
||||
});
|
||||
|
||||
it(`should return empty values as empty`, async () => {
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.headers.empty', '');
|
||||
});
|
||||
|
||||
it(`should throw when calling an undefined dynamic value`, async () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const vm = require('vm');
|
|||
const requireg = require('requireg');
|
||||
const deepmerge = require('deepmerge');
|
||||
const { toKebabCase, dynamicValueRegex, replaceInObject } = require('./shared');
|
||||
const isPlainObject = require('is-plain-object');
|
||||
|
||||
class Plugins {
|
||||
constructor(plugins = []) {
|
||||
|
|
@ -36,7 +37,7 @@ class Plugins {
|
|||
}
|
||||
|
||||
executeModifier(modifier, obj, orig) {
|
||||
let result = deepmerge({}, obj);
|
||||
let result = deepmerge({}, obj, { isMergeableObject: isPlainObject });
|
||||
|
||||
this.registry[modifier].forEach(
|
||||
modifier => (result = modifier(result, orig))
|
||||
|
|
@ -47,7 +48,25 @@ class Plugins {
|
|||
|
||||
replaceDynamicValues(obj) {
|
||||
return replaceInObject(obj, val => {
|
||||
let valIsEmpty = val.trim().length === 0;
|
||||
|
||||
if (valIsEmpty) {
|
||||
return val;
|
||||
}
|
||||
|
||||
try {
|
||||
let onlyHasDynamic =
|
||||
val.replace(dynamicValueRegex, '').trim() === '';
|
||||
|
||||
if (onlyHasDynamic) {
|
||||
let call;
|
||||
val.replace(dynamicValueRegex, (match, c) => {
|
||||
call = c;
|
||||
});
|
||||
|
||||
return vm.runInContext(call, this.context);
|
||||
}
|
||||
|
||||
return val.replace(dynamicValueRegex, (match, call) => {
|
||||
return vm.runInContext(call, this.context);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ class Request {
|
|||
'PARAMS',
|
||||
'FORM',
|
||||
'ALIAS',
|
||||
'COOKIEJAR'
|
||||
'COOKIEJAR',
|
||||
'FORMDATA'
|
||||
],
|
||||
req
|
||||
);
|
||||
|
|
@ -93,6 +94,7 @@ class Request {
|
|||
qs: this.PARAMS,
|
||||
body: this.PAYLOAD,
|
||||
form: this.FORM,
|
||||
formData: this.FORMDATA,
|
||||
|
||||
json: true,
|
||||
simple: false,
|
||||
|
|
@ -103,7 +105,8 @@ class Request {
|
|||
'headers',
|
||||
'qs',
|
||||
'body',
|
||||
'form'
|
||||
'form',
|
||||
'formData'
|
||||
]);
|
||||
|
||||
settings = this.plugins.replaceDynamicValues(settings);
|
||||
|
|
|
|||
Loading…
Reference in New Issue