Enabled parameters: mode, debug, cache-lookup, limit and contracts!
This commit is contained in:
parent
963aed8b8e
commit
6a88043e9c
|
@ -0,0 +1,24 @@
|
||||||
|
Status Embark plugin for MythX.
|
||||||
|
|
||||||
|
# QuickStart
|
||||||
|
|
||||||
|
Run `verify` in the Embark console. When the call returns, it will look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Embark (development) > verify
|
||||||
|
embark-mythx: Running MythX analysis in background.
|
||||||
|
embark-mythx: Submitting 'ERC20' for analysis...
|
||||||
|
embark-mythx: Submitting 'SafeMath' for analysis...
|
||||||
|
embark-mythx: Submitting 'Ownable' for analysis...
|
||||||
|
|
||||||
|
embark-mythx:
|
||||||
|
/home/flex/mythx-plugin/testToken/.embark/contracts/ERC20.sol
|
||||||
|
1:0 warning A floating pragma is set SWC-103
|
||||||
|
|
||||||
|
✖ 1 problem (0 errors, 1 warning)
|
||||||
|
|
||||||
|
embark-mythx: MythX analysis found vulnerabilities.
|
||||||
|
```
|
||||||
|
|
||||||
|
# Options
|
||||||
|
|
110
index.js
110
index.js
|
@ -1,4 +1,5 @@
|
||||||
const mythx = require('./mythx')
|
const mythx = require('./mythx')
|
||||||
|
const commandLineArgs = require('command-line-args')
|
||||||
|
|
||||||
module.exports = function(embark) {
|
module.exports = function(embark) {
|
||||||
|
|
||||||
|
@ -13,33 +14,47 @@ module.exports = function(embark) {
|
||||||
embark.registerConsoleCommand({
|
embark.registerConsoleCommand({
|
||||||
description: "Run MythX analysis",
|
description: "Run MythX analysis",
|
||||||
matches: (cmd) => {
|
matches: (cmd) => {
|
||||||
embark.logger.info('cmd', cmd)
|
//embark.logger.info('cmd', cmd)
|
||||||
const cmdName = cmd.match(/".*?"|\S+/g)
|
const cmdName = cmd.match(/".*?"|\S+/g)
|
||||||
embark.logger.info("cmd === 'verify': " + cmd === 'verify')
|
//embark.logger.info("cmdName", cmdName)
|
||||||
//embark.logger.info('cmdName.length === 1', cmdName.length === 1)
|
//embark.logger.info('cmdName.length === 1', cmdName.length === 1)
|
||||||
return (cmd === 'verify' && cmdName.length === 1)
|
//embark.logger.info("eh?")
|
||||||
|
return (Array.isArray(cmdName) &&
|
||||||
|
cmdName[0] === 'verify' &&
|
||||||
|
cmdName[1] != 'help' &&
|
||||||
|
cmdName[1] != 'status' &&
|
||||||
|
cmdName.length >= 1)
|
||||||
},
|
},
|
||||||
usage: "verify [options]",
|
usage: "verify [options]",
|
||||||
process: async (cmd, callback) => {
|
process: async (cmd, callback) => {
|
||||||
|
//embark.logger.info("cmd", cmd)
|
||||||
|
//embark.logger.info("verifying...")
|
||||||
|
|
||||||
|
const cmdName = cmd.match(/".*?"|\S+/g)
|
||||||
|
// Remove first element, as we know it's the command
|
||||||
|
cmdName.shift()
|
||||||
|
|
||||||
//embark.logger.info("embark.logger", JSON.stringify(embark.logger))
|
//embark.logger.info("embark.logger", JSON.stringify(embark.logger))
|
||||||
|
|
||||||
let cfg = parseOptions(cmd)
|
//console.log("option object", JSON.stringify({ "argv": cmdName }))
|
||||||
embark.logger.info('cmd', cmd)
|
let cfg = parseOptions({ "argv": cmdName })
|
||||||
embark.logger.info('cfg', JSON.stringify(cfg))
|
|
||||||
|
//embark.logger.info('cmd', cmdName)
|
||||||
|
//embark.logger.info('cfg', JSON.stringify(cfg))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
embark.logger.info("Running MythX analysis in background.")
|
embark.logger.info("Running MythX analysis in background.")
|
||||||
const result = await mythx(contracts, cfg, embark)
|
const returnCode = await mythx.analyse(contracts, cfg, embark)
|
||||||
//embark.logger.info("result", result)
|
//embark.logger.info("result", result)
|
||||||
|
|
||||||
if (returnCode === 0) {
|
if (returnCode === 0) {
|
||||||
return callback(null, "returnCode: " + returnCode)
|
return callback(null, "returnCode: " + returnCode)
|
||||||
} else if (returnCode === 1) {
|
} else if (returnCode === 1) {
|
||||||
embark.logger.error("Analysis returned with exit code 1.")
|
embark.logger.error("\nMythX analysis found vulnerabilities.")
|
||||||
return callback()
|
return callback()
|
||||||
} else {
|
} else {
|
||||||
//TODO: Figure out how to use error with callback properly.
|
//TODO: Figure out how to use error with callback properly.
|
||||||
return callback(new Error("Unexpected Error: return value of `analyze` should be either 0 or 1."), null)
|
return callback(new Error("\nUnexpected Error: return value of `analyze` should be either 0 or 1."), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -52,16 +67,17 @@ module.exports = function(embark) {
|
||||||
embark.registerConsoleCommand({
|
embark.registerConsoleCommand({
|
||||||
description: "Help",
|
description: "Help",
|
||||||
matches: (cmd) => {
|
matches: (cmd) => {
|
||||||
embark.logger.info('cmd', cmd)
|
//embark.logger.info('cmd', cmd)
|
||||||
const cmdName = cmd.match(/".*?"|\S+/g)
|
const cmdName = cmd.match(/".*?"|\S+/g)
|
||||||
embark.logger.info('cmdName', cmdName)
|
//embark.logger.info('cmdName', cmdName)
|
||||||
//embark.logger.info("cmdName[0] === 'verify' && cmdName[1] === 'help'", cmdName[0] === 'verify' && cmdName[1] === 'help')
|
//embark.logger.info("cmdName[0] === 'verify' && cmdName[1] === 'help'", cmdName[0] === 'verify' && cmdName[1] === 'help')
|
||||||
return (Array.isArray(cmdName) &&
|
return (Array.isArray(cmdName) &&
|
||||||
(cmdName[0] === 'verify' &&
|
(cmdName[0] === 'verify' &&
|
||||||
cmdName[1] === 'help'))
|
cmdName[1] === 'help'))
|
||||||
},
|
},
|
||||||
usage: "verify help",
|
usage: "verify help",
|
||||||
process: (cmd, callback) => {
|
process: (cmd, callback) => {
|
||||||
|
embark.logger.info("verify help running")
|
||||||
return callback(null, help())
|
return callback(null, help())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -75,9 +91,71 @@ module.exports = function(embark) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
embark.registerConsoleCommand({
|
||||||
|
description: "Check MythX analysis status",
|
||||||
|
matches: (cmd) => {
|
||||||
|
//embark.logger.info('cmd', cmd)
|
||||||
|
const cmdName = cmd.match(/".*?"|\S+/g)
|
||||||
|
//embark.logger.info("cmdName", cmdName)
|
||||||
|
//embark.logger.info('cmdName.length === 1', cmdName.length === 1)
|
||||||
|
return (Array.isArray(cmdName) &&
|
||||||
|
cmdName[0] === 'verify' &&
|
||||||
|
cmdName[1] != 'status' &&
|
||||||
|
cmdName.length == 3)
|
||||||
|
},
|
||||||
|
usage: "verify status <uuid>",
|
||||||
|
process: async (cmd, callback) => {
|
||||||
|
//embark.logger.info("verify status running")
|
||||||
|
//embark.logger.info("embark.logger", JSON.stringify(embark.logger))
|
||||||
|
|
||||||
|
let cfg = parseOptions(cmd)
|
||||||
|
//embark.logger.info('cmd', cmd)
|
||||||
|
//embark.logger.info('cfg', JSON.stringify(cfg))
|
||||||
|
try {
|
||||||
|
const returnCode = await mythx.getStatus(cfg, embark)
|
||||||
|
//embark.logger.info("result", result)
|
||||||
|
|
||||||
|
if (returnCode === 0) {
|
||||||
|
return callback(null, "returnCode: " + returnCode)
|
||||||
|
} else if (returnCode === 1) {
|
||||||
|
embark.logger.error("MythX analysis found vulnerabilities.")
|
||||||
|
return callback()
|
||||||
|
} else {
|
||||||
|
//TODO: Figure out how to use error with callback properly.
|
||||||
|
return callback(new Error("Unexpected Error: return value of `analyze` should be either 0 or 1."), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
embark.logger.error("error", e)
|
||||||
|
return callback(e, "ERR: " + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function parseOptions(options) {
|
function parseOptions(options) {
|
||||||
let config = {}
|
|
||||||
//TODO
|
//console.log("options", JSON.stringify(options))
|
||||||
return config
|
|
||||||
|
const optionDefinitions = [
|
||||||
|
{ name: 'full', alias: 'f', type: Boolean },
|
||||||
|
{ name: 'debug', alias: 'd', type: Boolean },
|
||||||
|
{ name: 'no-cache-lookup', alias: 'c', type: Boolean },
|
||||||
|
{ name: 'limit', alias: 'l', type: Number },
|
||||||
|
{ name: 'contracts', type: String, multiple: true, defaultOption: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
const parsed = commandLineArgs(optionDefinitions, options)
|
||||||
|
|
||||||
|
//console.log("parsed", JSON.stringify(parsed))
|
||||||
|
//console.log("parsed.contracts", parsed.contracts)
|
||||||
|
//console.log("parsed.full", parsed.full)
|
||||||
|
|
||||||
|
if(parsed.full) {
|
||||||
|
parsed.analysisMode = "full"
|
||||||
|
} else {
|
||||||
|
parsed.analysisMode = "full"
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -260,7 +260,7 @@ function doReport(config, objects, errors, notAnalyzedContracts) {
|
||||||
return config.debug || (log.level !== 'info');
|
return config.debug || (log.level !== 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return 1 if some vulenrabilities were found.
|
// Return 1 if vulnerabilities were found.
|
||||||
objects.forEach(ele => {
|
objects.forEach(ele => {
|
||||||
ele.issues.forEach(ele => {
|
ele.issues.forEach(ele => {
|
||||||
ret = ele.issues.length > 0 ? 1 : ret;
|
ret = ele.issues.length > 0 ? 1 : ret;
|
||||||
|
@ -287,15 +287,8 @@ function doReport(config, objects, errors, notAnalyzedContracts) {
|
||||||
const eslintIssuesByBaseName = groupEslintIssuesByBasename(eslintIssues);
|
const eslintIssuesByBaseName = groupEslintIssuesByBasename(eslintIssues);
|
||||||
|
|
||||||
const uniqueIssues = getUniqueIssues(eslintIssuesByBaseName);
|
const uniqueIssues = getUniqueIssues(eslintIssuesByBaseName);
|
||||||
|
|
||||||
console.log("uniqueIssues", JSON.stringify(uniqueIssues))
|
|
||||||
|
|
||||||
console.log("config.style", config.style)
|
|
||||||
const formatter = getFormatter(config.style);
|
const formatter = getFormatter(config.style);
|
||||||
|
|
||||||
config.logger.info("config.logger", JSON.stringify(config.logger))
|
|
||||||
const report = formatter(uniqueIssues);
|
const report = formatter(uniqueIssues);
|
||||||
console.log("report", report)
|
|
||||||
config.logger.info(report);
|
config.logger.info(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +308,7 @@ function doReport(config, objects, errors, notAnalyzedContracts) {
|
||||||
|
|
||||||
if (haveLogs) {
|
if (haveLogs) {
|
||||||
ret = 1;
|
ret = 1;
|
||||||
config.logger.info('MythX Logs:'.yellow);
|
config.logger.info('MythX Logs:');
|
||||||
logGroups.forEach(logGroup => {
|
logGroups.forEach(logGroup => {
|
||||||
config.logger.info(`\n${logGroup.sourcePath}`.yellow);
|
config.logger.info(`\n${logGroup.sourcePath}`.yellow);
|
||||||
config.logger.info(`UUID: ${logGroup.uuid}`.yellow);
|
config.logger.info(`UUID: ${logGroup.uuid}`.yellow);
|
||||||
|
@ -329,9 +322,9 @@ function doReport(config, objects, errors, notAnalyzedContracts) {
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
ret = 1;
|
ret = 1;
|
||||||
console.error('Internal MythX errors encountered:'.red);
|
config.logger.error('Internal MythX errors encountered:'.red);
|
||||||
errors.forEach(err => {
|
errors.forEach(err => {
|
||||||
console.error(err.error || err);
|
config.logger.error(err.error || err);
|
||||||
if (config.debug > 1 && err.stack) {
|
if (config.debug > 1 && err.stack) {
|
||||||
config.logger.info(err.stack);
|
config.logger.info(err.stack);
|
||||||
}
|
}
|
||||||
|
|
90
mythx.js
90
mythx.js
|
@ -8,7 +8,7 @@ const { MythXIssues, doReport } = require('./lib/issues2eslint');
|
||||||
|
|
||||||
const defaultAnalyzeRateLimit = 4
|
const defaultAnalyzeRateLimit = 4
|
||||||
|
|
||||||
module.exports = async function analyse(contracts, cfg, embark) {
|
async function analyse(contracts, cfg, embark) {
|
||||||
|
|
||||||
//embark.logger.debug("embark.config", embark.config)
|
//embark.logger.debug("embark.config", embark.config)
|
||||||
|
|
||||||
|
@ -39,14 +39,34 @@ module.exports = async function analyse(contracts, cfg, embark) {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
//TODO: Check contract names provided in options are respected
|
//Check contract names provided in options are respected
|
||||||
//const contractNames = cfg._.length > 1 ? cfg._.slice(1, cfg._.length) : null
|
|
||||||
|
|
||||||
//embark.logger.info("contracts", contracts)
|
//embark.logger.info("contracts", contracts)
|
||||||
|
//embark.logger.info("cfg.contracts", cfg.contracts)
|
||||||
|
|
||||||
const submitObjects = mythXUtil.buildRequestData(contracts)
|
// Filter contracts based on parameter choice
|
||||||
|
let toSubmit = {};
|
||||||
|
if(cfg.contracts) {
|
||||||
|
toSubmit.sources = contracts.sources
|
||||||
|
toSubmit.contracts = {}
|
||||||
|
for (let [filename, contractObjects] of Object.entries(contracts.contracts)) {
|
||||||
|
for (let [contractName, contract] of Object.entries(contractObjects)) {
|
||||||
|
if (cfg.contracts.indexOf(contractName) >= 0) {
|
||||||
|
//console.log("Adding to submit", contractName, contractObjects)
|
||||||
|
if(!toSubmit.contracts[filename]) {
|
||||||
|
toSubmit.contracts[filename] = {}
|
||||||
|
}
|
||||||
|
toSubmit.contracts[filename][contractName] = contract ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toSubmit = contracts
|
||||||
|
}
|
||||||
|
|
||||||
process.exit(0)
|
//embark.logger.info("toSubmit", toSubmit)
|
||||||
|
const submitObjects = mythXUtil.buildRequestData(toSubmit)
|
||||||
|
|
||||||
|
//return 0
|
||||||
const { objects, errors } = await doAnalysis(armletClient, cfg, submitObjects, null, limit)
|
const { objects, errors } = await doAnalysis(armletClient, cfg, submitObjects, null, limit)
|
||||||
|
|
||||||
//console.log("objects", JSON.stringify(objects))
|
//console.log("objects", JSON.stringify(objects))
|
||||||
|
@ -57,14 +77,42 @@ module.exports = async function analyse(contracts, cfg, embark) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getStatus(cfg, embark) {
|
||||||
|
|
||||||
|
//embark.logger.debug("embark.config", embark.config)
|
||||||
|
|
||||||
|
//console.log("embark.logger", embark.logger)
|
||||||
|
//console.log("JSON.stringify(embark.logger)", JSON.stringify(embark.logger))
|
||||||
|
//embark.logger.info("typeof embark.logger", typeof embark.logger)
|
||||||
|
cfg.logger = embark.logger
|
||||||
|
//embark.logger.info("embark", JSON.stringify(embark))
|
||||||
|
|
||||||
|
// Connect to MythX via armlet
|
||||||
|
const armletClient = new armlet.Client(
|
||||||
|
{
|
||||||
|
clientToolName: "embark-mythx",
|
||||||
|
password: process.env.MYTHX_PASSWORD,
|
||||||
|
ethAddress: process.env.MYTHX_ETH_ADDRESS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cfg.uuid) {
|
||||||
|
try {
|
||||||
|
const results = await armletClient.getIssues(config.uuid);
|
||||||
|
return ghettoReport(embark.logger.info, results);
|
||||||
|
} catch (err) {
|
||||||
|
embark.logger.warn(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const doAnalysis = async (armletClient, config, contracts, contractNames = null, limit) => {
|
const doAnalysis = async (armletClient, config, contracts, contractNames = null, limit) => {
|
||||||
|
|
||||||
//config.logger.info("\ncontracts", contracts)
|
//config.logger.info("\ncontracts", contracts)
|
||||||
|
|
||||||
const timeout = (config.timeout || 300) * 1000;
|
const timeout = (config.timeout || 300) * 1000;
|
||||||
const initialDelay = ('initial-delay' in config) ? config['initial-delay'] * 1000 : undefined;
|
const initialDelay = ('initial-delay' in config) ? config['initial-delay'] * 1000 : undefined;
|
||||||
//const cacheLookup = ('cache-lookup' in config) ? config['cache-lookup'] : true;
|
const cacheLookup = ('cache-lookup' in config) ? config['cache-lookup'] : true;
|
||||||
const cacheLookup = false
|
|
||||||
|
|
||||||
const results = await asyncPool(limit, contracts, async buildObj => {
|
const results = await asyncPool(limit, contracts, async buildObj => {
|
||||||
|
|
||||||
|
@ -85,7 +133,7 @@ const doAnalysis = async (armletClient, config, contracts, contractNames = null,
|
||||||
|
|
||||||
// request analysis to armlet.
|
// request analysis to armlet.
|
||||||
try {
|
try {
|
||||||
//config.logger.info("analyzeOpts", JSON.stringify(analyzeOpts))
|
config.logger.info("Submitting '" + obj.contractName + "' for analysis...")
|
||||||
const armletResult = await armletClient.analyzeWithStatus(analyzeOpts);
|
const armletResult = await armletClient.analyzeWithStatus(analyzeOpts);
|
||||||
//config.logger.info("armletResult", JSON.stringify(armletResult))
|
//config.logger.info("armletResult", JSON.stringify(armletResult))
|
||||||
const {issues, status} = armletResult
|
const {issues, status} = armletResult
|
||||||
|
@ -143,3 +191,27 @@ const doAnalysis = async (armletClient, config, contracts, contractNames = null,
|
||||||
return accum;
|
return accum;
|
||||||
}, { errors: [], objects: [] });
|
}, { errors: [], objects: [] });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ghettoReport(logger, results) {
|
||||||
|
let issuesCount = 0;
|
||||||
|
results.forEach(ele => {
|
||||||
|
issuesCount += ele.issues.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issuesCount === 0) {
|
||||||
|
logger('No issues found');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (const group of results) {
|
||||||
|
logger(group.sourceList.join(', ').underline);
|
||||||
|
for (const issue of group.issues) {
|
||||||
|
logger(yaml.safeDump(issue, {'skipInvalid': true}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
analyse,
|
||||||
|
getStatus
|
||||||
|
}
|
|
@ -116,6 +116,11 @@
|
||||||
"request": "^2.88.0"
|
"request": "^2.88.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"array-back": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q=="
|
||||||
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
|
@ -356,6 +361,17 @@
|
||||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz",
|
||||||
"integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw=="
|
"integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw=="
|
||||||
},
|
},
|
||||||
|
"command-line-args": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==",
|
||||||
|
"requires": {
|
||||||
|
"array-back": "^3.0.1",
|
||||||
|
"find-replace": "^3.0.0",
|
||||||
|
"lodash.camelcase": "^4.3.0",
|
||||||
|
"typical": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
@ -1073,6 +1089,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||||
},
|
},
|
||||||
|
"find-replace": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
|
||||||
|
"requires": {
|
||||||
|
"array-back": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"find-up": {
|
"find-up": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||||
|
@ -1717,6 +1741,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||||
},
|
},
|
||||||
|
"lodash.camelcase": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
|
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||||
|
},
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
|
@ -2586,6 +2615,11 @@
|
||||||
"prelude-ls": "~1.1.2"
|
"prelude-ls": "~1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"typical": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw=="
|
||||||
|
},
|
||||||
"uri-js": {
|
"uri-js": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"armlet": "^2.3.0",
|
"armlet": "^2.3.0",
|
||||||
|
"command-line-args": "^5.1.1",
|
||||||
"dotenv": "^7.0.0",
|
"dotenv": "^7.0.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
|
Loading…
Reference in New Issue