create a cli with python. python will be installed by default on a lot of system. * use getpass python native module Fixes: https://github.com/lesspass/lesspass/issues/305 * use lesspass python code generator from Maurits van der Schee Fixes: https://github.com/lesspass/lesspass/issues/328 tested with python2.7 and python3.6 breaking changes: * -c is associated with --copy flag to copy the password * -C is associated with the counter (previously -c)pull/364/head
@@ -16,6 +16,16 @@ LessPass open source password manager (https://lesspass.com) | |||
* :fr: [Présentation de LessPass](https://www.youtube.com/watch?v=YbaRCHXk8Jo) | |||
## CLI | |||
Use pip to install LessPass cli: | |||
python3 -m pip install --user lesspass | |||
Usage | |||
lesspass --help | |||
## License | |||
This project is licensed under the terms of the GNU GPLv3. | |||
@@ -0,0 +1,115 @@ | |||
# Byte-compiled / optimized / DLL files | |||
__pycache__/ | |||
*.py[cod] | |||
*$py.class | |||
# C extensions | |||
*.so | |||
# Distribution / packaging | |||
.Python | |||
build/ | |||
develop-eggs/ | |||
dist/ | |||
downloads/ | |||
eggs/ | |||
.eggs/ | |||
lib/ | |||
lib64/ | |||
parts/ | |||
sdist/ | |||
var/ | |||
wheels/ | |||
share/python-wheels/ | |||
*.egg-info/ | |||
.installed.cfg | |||
*.egg | |||
MANIFEST | |||
# PyInstaller | |||
# Usually these files are written by a python script from a template | |||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||
*.manifest | |||
*.spec | |||
# Installer logs | |||
pip-log.txt | |||
pip-delete-this-directory.txt | |||
# Unit test / coverage reports | |||
htmlcov/ | |||
.tox/ | |||
.nox/ | |||
.coverage | |||
.coverage.* | |||
.cache | |||
nosetests.xml | |||
coverage.xml | |||
*.cover | |||
.hypothesis/ | |||
.pytest_cache/ | |||
# Translations | |||
*.mo | |||
*.pot | |||
# Django stuff: | |||
*.log | |||
local_settings.py | |||
db.sqlite3 | |||
# Flask stuff: | |||
instance/ | |||
.webassets-cache | |||
# Scrapy stuff: | |||
.scrapy | |||
# Sphinx documentation | |||
docs/_build/ | |||
# PyBuilder | |||
target/ | |||
# Jupyter Notebook | |||
.ipynb_checkpoints | |||
# IPython | |||
profile_default/ | |||
ipython_config.py | |||
# pyenv | |||
.python-version | |||
# celery beat schedule file | |||
celerybeat-schedule | |||
# SageMath parsed files | |||
*.sage.py | |||
# Environments | |||
.env | |||
.venv | |||
env/ | |||
venv/ | |||
ENV/ | |||
env.bak/ | |||
venv.bak/ | |||
# Spyder project settings | |||
.spyderproject | |||
.spyproject | |||
# Rope project settings | |||
.ropeproject | |||
# mkdocs documentation | |||
/site | |||
# mypy | |||
.mypy_cache/ | |||
.dmypy.json | |||
dmypy.json | |||
# Pyre type checker | |||
.pyre/ |
@@ -0,0 +1 @@ | |||
name = "lesspass" |
@@ -0,0 +1,29 @@ | |||
import argparse | |||
import os | |||
def parse_args(args): | |||
parser = argparse.ArgumentParser(add_help=False) | |||
parser.add_argument("site", nargs="?") | |||
parser.add_argument("login", nargs="?") | |||
parser.add_argument( | |||
"master_password", | |||
default=os.environ.get("LESSPASS_MASTER_PASSWORD", None), | |||
nargs="?", | |||
) | |||
parser.add_argument("-l", "--lowercase", dest="l", action="store_true") | |||
parser.add_argument("-u", "--uppercase", dest="u", action="store_true") | |||
parser.add_argument("-d", "--digits", dest="d", action="store_true") | |||
parser.add_argument("-s", "--symbols", dest="s", action="store_true") | |||
parser.add_argument("--no-lowercase", dest="nl", action="store_true") | |||
parser.add_argument("--no-uppercase", dest="nu", action="store_true") | |||
parser.add_argument("--no-digits", dest="nd", action="store_true") | |||
parser.add_argument("--no-symbols", dest="ns", action="store_true") | |||
parser.add_argument("-L", "--length", default=16, type=int) | |||
parser.add_argument("-C", "--counter", default=1, type=int) | |||
parser.add_argument( | |||
"-c", "--copy", "--clipboard", dest="clipboard", action="store_true" | |||
) | |||
parser.add_argument("-h", "--help", action="store_true") | |||
parser.add_argument("-v", "--version", action="store_true") | |||
return parser.parse_args(args) |
@@ -0,0 +1,48 @@ | |||
import platform | |||
import subprocess | |||
import uuid | |||
def _call(args): | |||
return subprocess.call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |||
def _copy_available(command): | |||
if platform.system() == "Windows": | |||
return _call(["where", command]) == 0 | |||
return _call(["which", command]) == 0 | |||
def get_system_copy_command(): | |||
if platform.system() == "Windows" and _copy_available("clip"): | |||
return "clip" | |||
if platform.system() == "Darwin" and _copy_available("pbcopy"): | |||
return "pbcopy" | |||
for command in ["xsel", "xclip"]: | |||
if _copy_available(command): | |||
return command | |||
def _popen(args, **kwargs): | |||
return subprocess.Popen(args, stdin=subprocess.PIPE, encoding="utf8") | |||
commands = { | |||
"clip": ["clip"], | |||
"pbcopy": ["pbcopy"], | |||
"xsel": ["xsel", "--clipboard", "--input"], | |||
"xclip": ["xclip", "-selection", "clipboard"], | |||
} | |||
def copy(text): | |||
command = get_system_copy_command() | |||
if command is None: | |||
raise (Exception("No software available on your system to copy to clipboard")) | |||
args = commands[command] | |||
if platform.system() == "Windows": | |||
p = _popen(args) | |||
else: | |||
p = _popen(args, close_fds=True) | |||
p.communicate(input=text) |
@@ -0,0 +1,45 @@ | |||
import getpass | |||
import platform | |||
import sys | |||
import traceback | |||
from lesspass.version import __version__ | |||
from lesspass.cli import parse_args | |||
from lesspass.help import print_help | |||
from lesspass.validator import validate_args | |||
from lesspass.profile import create_profile | |||
from lesspass.password import generate_password | |||
from lesspass.clipboard import copy | |||
def main(args=sys.argv[1:]): | |||
args = parse_args(args) | |||
if args.version: | |||
print(__version__) | |||
sys.exit(0) | |||
error, help_message = validate_args(args) | |||
if args.help: | |||
print_help(help_message, long=True) | |||
sys.exit(0) | |||
if error: | |||
print_help(help_message) | |||
sys.exit(0) | |||
profile, master_password = create_profile(args) | |||
if not master_password: | |||
master_password = getpass.getpass("Master Password: ") | |||
generated_password = generate_password(profile, master_password) | |||
if args.clipboard: | |||
try: | |||
copy(generated_password) | |||
print("Copied to clipboard") | |||
except Exception as e: | |||
print("Copy failed, we are sorry") | |||
print("Can you send us an email at contact@lesspass.com\n") | |||
print("-" * 80) | |||
print("Object: [LessPass][cli] Copy issue on %s" % platform.system()) | |||
print("Hello,") | |||
print("I got an issue with LessPass cli software.\n") | |||
traceback.print_exc() | |||
print("-" * 80) | |||
else: | |||
print(generated_password) |
@@ -0,0 +1,72 @@ | |||
usage = "Usage: lesspass SITE [LOGIN] [MASTER_PASSWORD] [OPTIONS]" | |||
def get_short_help(help_message): | |||
return "%s\nErrors:\n%s\nTry 'lesspass --help' for more information." % ( | |||
usage, | |||
help_message, | |||
) | |||
def get_long_help(): | |||
return """Name: | |||
LessPass - stateless password generator | |||
Usage: | |||
lesspass SITE [LOGIN] [MASTER_PASSWORD] [OPTIONS] | |||
Arguments: | |||
SITE site used in the password generation (required) | |||
LOGIN login used in the password generation | |||
default to '' if not provided | |||
MASTER_PASSWORD master password used in password generation | |||
default to LESSPASS_MASTER_PASSWORD env variable or prompt | |||
Options: | |||
-l, --lowercase add lowercase in password | |||
-u, --uppercase add uppercase in password | |||
-d, --digits add digits in password | |||
-s, --symbols add symbols in password | |||
-L, --length int (default 16) | |||
-C, --counter int (default 1) | |||
--no-lowercase remove lowercase from password | |||
--no-uppercase remove uppercase from password | |||
--no-digits remove digits from password | |||
--no-symbols remove symbols from password | |||
-c, --clipboard copy generated password to clipboard rather than displaying it. | |||
Need pbcopy (OSX), xsel or xclip (Linux) or clip (Windows). | |||
Examples: | |||
# no symbols | |||
lesspass site login masterpassword --no-symbols | |||
# no symbols shortcut | |||
lesspass site login masterpassword -lud | |||
# only digits and length of 8 | |||
lesspass site login masterpassword -d -L8 | |||
# master password in env variable | |||
LESSPASS_MASTER_PASSWORD="masterpassword" lesspass site login | |||
Copyright: | |||
Copyright © 2018 Guillaume Vincent <contact@lesspass.com>. License GPLv3: GNU GPL version 3 <https://gnu.org/licenses/gpl.html>. | |||
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.""" | |||
def print_help(help_message, long=False): | |||
usage = "Usage: lesspass SITE [LOGIN] [MASTER_PASSWORD] [OPTIONS]" | |||
short_help = "%s\nErrors:\n%s\nTry 'lesspass --help' for more information." % ( | |||
usage, | |||
help_message, | |||
) | |||
if long: | |||
print(get_long_help()) | |||
else: | |||
print(short_help) |
@@ -0,0 +1,108 @@ | |||
# From an initial work from Maurits van der Schee | |||
# https://github.com/mevdschee/lesspass.py | |||
# | |||
# Copyright (C) 2017 Maurits van der Schee | |||
# Copyright (C) 2018 Guillaume Vincent | |||
# | |||
# This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU General Public License as published by | |||
# the Free Software Foundation, either version 3 of the License, or | |||
# (at your option) any later version. | |||
# | |||
# This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
# GNU General Public License for more details. | |||
# | |||
# You should have received a copy of the GNU General Public License | |||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
import hashlib | |||
import binascii | |||
CHARACTER_SUBSETS = { | |||
"lowercase": "abcdefghijklmnopqrstuvwxyz", | |||
"uppercase": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | |||
"digits": "0123456789", | |||
"symbols": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", | |||
} | |||
def _calc_entropy(password_profile, master_password): | |||
salt = ( | |||
password_profile["site"] | |||
+ password_profile["login"] | |||
+ hex(password_profile["counter"])[2:] | |||
) | |||
return binascii.hexlify( | |||
hashlib.pbkdf2_hmac( | |||
"sha256", master_password.encode("utf-8"), salt.encode("utf-8"), 100000, 32 | |||
) | |||
) | |||
def _get_set_of_characters(rules=None): | |||
if rules is None: | |||
return ( | |||
CHARACTER_SUBSETS["lowercase"] | |||
+ CHARACTER_SUBSETS["uppercase"] | |||
+ CHARACTER_SUBSETS["digits"] | |||
+ CHARACTER_SUBSETS["symbols"] | |||
) | |||
set_of_chars = "" | |||
for rule in rules: | |||
set_of_chars += CHARACTER_SUBSETS[rule] | |||
return set_of_chars | |||
def _consume_entropy(generated_password, quotient, set_of_characters, max_length): | |||
if len(generated_password) >= max_length: | |||
return [generated_password, quotient] | |||
quotient, remainder = divmod(quotient, len(set_of_characters)) | |||
generated_password += set_of_characters[remainder] | |||
return _consume_entropy(generated_password, quotient, set_of_characters, max_length) | |||
def _insert_string_pseudo_randomly(generated_password, entropy, string): | |||
for letter in string: | |||
quotient, remainder = divmod(entropy, len(generated_password)) | |||
generated_password = ( | |||
generated_password[:remainder] + letter + generated_password[remainder:] | |||
) | |||
entropy = quotient | |||
return generated_password | |||
def _get_one_char_per_rule(entropy, rules): | |||
one_char_per_rules = "" | |||
for rule in rules: | |||
value, entropy = _consume_entropy("", entropy, CHARACTER_SUBSETS[rule], 1) | |||
one_char_per_rules += value | |||
return [one_char_per_rules, entropy] | |||
def _get_configured_rules(password_profile): | |||
rules = ["lowercase", "uppercase", "digits", "symbols"] | |||
return [ | |||
rule for rule in rules if rule in password_profile and password_profile[rule] | |||
] | |||
def _render_password(entropy, password_profile): | |||
rules = _get_configured_rules(password_profile) | |||
set_of_characters = _get_set_of_characters(rules) | |||
password, password_entropy = _consume_entropy( | |||
"", int(entropy, 16), set_of_characters, password_profile["length"] - len(rules) | |||
) | |||
characters_to_add, character_entropy = _get_one_char_per_rule( | |||
password_entropy, rules | |||
) | |||
return _insert_string_pseudo_randomly( | |||
password, character_entropy, characters_to_add | |||
) | |||
def generate_password(password_profile, master_password): | |||
entropy = _calc_entropy(password_profile, master_password) | |||
return _render_password(entropy, password_profile) |
@@ -0,0 +1,17 @@ | |||
def create_profile(args): | |||
profile = { | |||
"lowercase": False if args.nl else True, | |||
"uppercase": False if args.nu else True, | |||
"digits": False if args.nd else True, | |||
"symbols": False if args.ns else True, | |||
"length": args.length, | |||
"counter": args.counter, | |||
"site": args.site, | |||
"login": args.login or "", | |||
} | |||
if args.l or args.u or args.d or args.s: | |||
profile["lowercase"] = args.l | |||
profile["uppercase"] = args.u | |||
profile["digits"] = args.d | |||
profile["symbols"] = args.s | |||
return profile, args.master_password |
@@ -0,0 +1,68 @@ | |||
from lesspass.clipboard import get_system_copy_command | |||
class NoOppositeRules(object): | |||
def __init__(self, args): | |||
self.args = args | |||
self.error_message = "" | |||
def is_valid(self): | |||
is_valid = True | |||
if self.args.l and self.args.nl: | |||
self.error_message += ( | |||
" * Can't have -l (--lowercase) and --no-lowercase at the same time" | |||
) | |||
is_valid = False | |||
if self.args.u and self.args.nu: | |||
self.error_message += ( | |||
" * Can't have -u (--uppercase) and --no-uppercase at the same time" | |||
) | |||
is_valid = False | |||
if self.args.d and self.args.nd: | |||
self.error_message += ( | |||
" * Can't have -d (--digits) and --no-digits at the same time" | |||
) | |||
is_valid = False | |||
if self.args.s and self.args.ns: | |||
self.error_message += ( | |||
" * Can't have -s (--symbols) and --no-symbols at the same time" | |||
) | |||
is_valid = False | |||
return is_valid | |||
class DefaultParameters(object): | |||
def __init__(self, args): | |||
self.args = args | |||
self.error_message = "" | |||
def is_valid(self): | |||
is_valid = True | |||
if not self.args.site: | |||
self.error_message += " * SITE is a required argument" | |||
is_valid = False | |||
return is_valid | |||
class ClipboardAvailable(object): | |||
def __init__(self, args): | |||
self.args = args | |||
self.error_message = "" | |||
def is_valid(self): | |||
is_valid = True | |||
if self.args.clipboard and not get_system_copy_command(): | |||
self.error_message += " * To use the option -c (--copy) you need pbcopy on OSX, xsel or xclip on Linux and clip on Windows" | |||
is_valid = False | |||
return is_valid | |||
def validate_args(args): | |||
rules = [NoOppositeRules(args), DefaultParameters(args), ClipboardAvailable(args)] | |||
error = False | |||
error_message = "" | |||
for rule in rules: | |||
if not rule.is_valid(): | |||
error = True | |||
error_message += "%s\n" % rule.error_message | |||
return error, error_message |
@@ -0,0 +1 @@ | |||
__version__ = '6.0.0' |
@@ -0,0 +1,27 @@ | |||
import setuptools | |||
from lesspass.version import __version__ | |||
from lesspass.help import get_long_help | |||
setuptools.setup( | |||
name='lesspass', | |||
version=__version__, | |||
packages=['lesspass'], | |||
author='Guillaume Vincent', | |||
author_email='contact@lesspass.com', | |||
description='LessPass stateless password generator', | |||
long_description=get_long_help(), | |||
install_requires=[], | |||
entry_points=""" | |||
[console_scripts] | |||
lesspass=lesspass.core:main | |||
""", | |||
url='https://github.com/lesspass/lesspass', | |||
license='GPL-3.0', | |||
classifiers=[ | |||
"Programming Language :: Python", | |||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", | |||
"Operating System :: OS Independent", | |||
], | |||
) |
@@ -0,0 +1,4 @@ | |||
tox | |||
pytest | |||
flake8 | |||
mock |
@@ -0,0 +1,112 @@ | |||
import unittest | |||
from mock import patch | |||
from lesspass.cli import parse_args | |||
class TestParseArgs(unittest.TestCase): | |||
def test_parse_args_version(self): | |||
self.assertTrue(parse_args(["--version"]).version) | |||
self.assertTrue(parse_args(["-v"]).version) | |||
def test_parse_args_help(self): | |||
self.assertTrue(parse_args(["--help"]).help) | |||
self.assertTrue(parse_args(["-h"]).help) | |||
def test_parse_args_site(self): | |||
self.assertEqual(parse_args(["site"]).site, "site") | |||
def test_parse_args_login(self): | |||
self.assertEqual(parse_args(["site", "login"]).login, "login") | |||
def test_parse_args_LESSPASS_MASTER_PASSWORD_env_variable(self): | |||
with patch.dict("os.environ", {"LESSPASS_MASTER_PASSWORD": "password"}): | |||
self.assertEqual(parse_args([]).master_password, "password") | |||
def test_parse_args_master_password(self): | |||
self.assertEqual( | |||
parse_args(["site", "login", "masterpassword"]).master_password, | |||
"masterpassword", | |||
) | |||
def test_parse_args_l(self): | |||
self.assertTrue(parse_args(["site", "-l"]).l) | |||
self.assertTrue(parse_args(["site", "--lowercase"]).l) | |||
def test_parse_args_u(self): | |||
self.assertTrue(parse_args(["site", "-u"]).u) | |||
self.assertTrue(parse_args(["site", "--uppercase"]).u) | |||
def test_parse_args_d(self): | |||
self.assertTrue(parse_args(["site", "-d"]).d) | |||
self.assertTrue(parse_args(["site", "--digits"]).d) | |||
def test_parse_args_s(self): | |||
self.assertTrue(parse_args(["site", "-s"]).s) | |||
self.assertTrue(parse_args(["site", "--symbols"]).s) | |||
def test_parse_args_lu(self): | |||
args = parse_args(["site", "-lu"]) | |||
self.assertTrue(args.l) | |||
self.assertTrue(args.u) | |||
self.assertFalse(args.d) | |||
self.assertFalse(args.s) | |||
def test_parse_args_lud(self): | |||
args = parse_args(["site", "-lud"]) | |||
self.assertTrue(args.l) | |||
self.assertTrue(args.u) | |||
self.assertTrue(args.d) | |||
self.assertFalse(args.s) | |||
def test_parse_args_luds(self): | |||
args = parse_args(["site", "-luds"]) | |||
self.assertTrue(args.l) | |||
self.assertTrue(args.u) | |||
self.assertTrue(args.d) | |||
self.assertTrue(args.s) | |||
def test_parse_args_no_lowercase(self): | |||
self.assertTrue(parse_args(["site", "--no-lowercase"]).nl) | |||
def test_parse_args_no_uppercase(self): | |||
self.assertTrue(parse_args(["site", "--no-uppercase"]).nu) | |||
def test_parse_args_no_digits(self): | |||
self.assertTrue(parse_args(["site", "--no-digits"]).nd) | |||
def test_parse_args_no_symbols(self): | |||
self.assertTrue(parse_args(["site", "--no-symbols"]).ns) | |||
def test_parse_args_length_default(self): | |||
self.assertEqual(parse_args(["site"]).length, 16) | |||
def test_parse_args_length_long(self): | |||
self.assertEqual(parse_args(["site", "--length", "8"]).length, 8) | |||
def test_parse_args_length_short(self): | |||
self.assertEqual(parse_args(["site", "-L6"]).length, 6) | |||
self.assertEqual(parse_args(["site", "-L", "12"]).length, 12) | |||
def test_parse_args_counter_default(self): | |||
self.assertEqual(parse_args(["site"]).counter, 1) | |||
def test_parse_args_counter_long(self): | |||
self.assertEqual(parse_args(["site", "--counter", "2"]).counter, 2) | |||
def test_parse_args_counter_short(self): | |||
self.assertEqual(parse_args(["site", "-C99"]).counter, 99) | |||
self.assertEqual(parse_args(["site", "-C", "100"]).counter, 100) | |||
def test_parse_args_clipboard_default(self): | |||
self.assertFalse(parse_args(["site"]).clipboard) | |||
def test_parse_args_clipboard_long(self): | |||
self.assertTrue(parse_args(["site", "--copy"]).clipboard) | |||
def test_parse_args_clipboard_short(self): | |||
self.assertTrue(parse_args(["site", "-c"]).clipboard) | |||
def test_parse_args_clipboard_backward_compatibility(self): | |||
self.assertTrue(parse_args(["site", "--clipboard"]).clipboard) |
@@ -0,0 +1,83 @@ | |||
import unittest | |||
from lesspass.password import generate_password | |||
class TestPassword(unittest.TestCase): | |||
def test_generate_password(self): | |||
profile = { | |||
"site": "example.org", | |||
"login": "contact@example.org", | |||
"lowercase": True, | |||
"uppercase": True, | |||
"digits": True, | |||
"symbols": True, | |||
"length": 16, | |||
"counter": 1, | |||
} | |||
master_password = "password" | |||
self.assertEqual( | |||
generate_password(profile, master_password), "WHLpUL)e00[iHR+w" | |||
) | |||
def test_generate_password_2(self): | |||
profile = { | |||
"site": "example.org", | |||
"login": "contact@example.org", | |||
"lowercase": True, | |||
"uppercase": True, | |||
"digits": True, | |||
"symbols": False, | |||
"length": 14, | |||
"counter": 2, | |||
} | |||
master_password = "password" | |||
self.assertEqual(generate_password(profile, master_password), "MBAsB7b1Prt8Sl") | |||
def test_generate_password_3(self): | |||
profile = { | |||
"site": "example.org", | |||
"login": "contact@example.org", | |||
"lowercase": False, | |||
"uppercase": False, | |||
"digits": True, | |||
"symbols": False, | |||
"length": 16, | |||
"counter": 1, | |||
} | |||
master_password = "password" | |||
self.assertEqual( | |||
generate_password(profile, master_password), "8742368585200667" | |||
) | |||
def test_generate_password_4(self): | |||
profile = { | |||
"site": "example.org", | |||
"login": "contact@example.org", | |||
"lowercase": True, | |||
"uppercase": True, | |||
"digits": False, | |||
"symbols": True, | |||
"length": 16, | |||
"counter": 1, | |||
} | |||
master_password = "password" | |||
self.assertEqual( | |||
generate_password(profile, master_password), "s>{F}RwkN/-fmM.X" | |||
) | |||
def test_generate_password_nrt_328(self): | |||
profile = { | |||
"site": "site", | |||
"login": "login", | |||
"lowercase": True, | |||
"uppercase": True, | |||
"digits": True, | |||
"symbols": True, | |||
"length": 16, | |||
"counter": 10, | |||
} | |||
master_password = "test" | |||
self.assertEqual( | |||
generate_password(profile, master_password), "XFt0F*,r619:+}[." | |||
) |
@@ -0,0 +1,169 @@ | |||
import unittest | |||
from lesspass.cli import parse_args | |||
from lesspass.profile import create_profile | |||
class TestProfile(unittest.TestCase): | |||
def test_create_profile_default(self): | |||
profile, master_password = create_profile(parse_args(["site", "login"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
self.assertEqual(profile["length"], 16) | |||
self.assertEqual(profile["counter"], 1) | |||
self.assertEqual(profile["site"], "site") | |||
self.assertEqual(profile["login"], "login") | |||
self.assertIsNone(master_password) | |||
def test_create_profile_login(self): | |||
profile, _ = create_profile(parse_args(["site"])) | |||
self.assertEqual(profile["login"], "") | |||
def test_create_profile_length(self): | |||
profile, _ = create_profile(parse_args(["site", "--length", "8"])) | |||
self.assertEqual(profile["length"], 8) | |||
def test_create_profile_counter(self): | |||
profile, _ = create_profile(parse_args(["site", "--counter", "2"])) | |||
self.assertEqual(profile["counter"], 2) | |||
def test_create_profile_master_password(self): | |||
_, master_password = create_profile( | |||
parse_args(["site", "login", "master_password"]) | |||
) | |||
self.assertEqual(master_password, "master_password") | |||
def test_create_profile_l(self): | |||
profile, _ = create_profile(parse_args(["site", "-l"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_u(self): | |||
profile, _ = create_profile(parse_args(["site", "-u"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_d(self): | |||
profile, _ = create_profile(parse_args(["site", "-d"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_s(self): | |||
profile, _ = create_profile(parse_args(["site", "-s"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_lu(self): | |||
profile, _ = create_profile(parse_args(["site", "-lu"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_ld(self): | |||
profile, _ = create_profile(parse_args(["site", "-ld"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_ls(self): | |||
profile, _ = create_profile(parse_args(["site", "-ls"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_ud(self): | |||
profile, _ = create_profile(parse_args(["site", "-ud"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_us(self): | |||
profile, _ = create_profile(parse_args(["site", "-us"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_ds(self): | |||
profile, _ = create_profile(parse_args(["site", "-ds"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_lud(self): | |||
profile, _ = create_profile(parse_args(["site", "-lud"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) | |||
def test_create_profile_lus(self): | |||
profile, _ = create_profile(parse_args(["site", "-lus"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_uds(self): | |||
profile, _ = create_profile(parse_args(["site", "-uds"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_luds(self): | |||
profile, _ = create_profile(parse_args(["site", "-luds"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_suld(self): | |||
profile, _ = create_profile(parse_args(["site", "-suld"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_nl(self): | |||
profile, _ = create_profile(parse_args(["site", "--no-lowercase"])) | |||
self.assertFalse(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_nu(self): | |||
profile, _ = create_profile(parse_args(["site", "--no-uppercase"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertFalse(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_nd(self): | |||
profile, _ = create_profile(parse_args(["site", "--no-digits"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertFalse(profile["digits"]) | |||
self.assertTrue(profile["symbols"]) | |||
def test_create_profile_ns(self): | |||
profile, _ = create_profile(parse_args(["site", "--no-symbols"])) | |||
self.assertTrue(profile["lowercase"]) | |||
self.assertTrue(profile["uppercase"]) | |||
self.assertTrue(profile["digits"]) | |||
self.assertFalse(profile["symbols"]) |
@@ -0,0 +1,52 @@ | |||
import unittest | |||
from lesspass.cli import parse_args | |||
from lesspass.validator import validate_args | |||
class TestValidateArgs(unittest.TestCase): | |||
def test_validate_args_no_opposite_rules_lowercase(self): | |||
error, message = validate_args(parse_args(["site", "-l", "--no-lowercase"])) | |||
self.assertTrue(error) | |||
self.assertTrue( | |||
"Can't have -l (--lowercase) and --no-lowercase at the same time" in message | |||
) | |||
def test_validate_args_no_opposite_rules_uppercase(self): | |||
error, message = validate_args(parse_args(["site", "-u", "--no-uppercase"])) | |||
self.assertTrue(error) | |||
self.assertTrue( | |||
"Can't have -u (--uppercase) and --no-uppercase at the same time" in message | |||
) | |||
def test_validate_args_no_opposite_rules_digits(self): | |||
error, message = validate_args(parse_args(["site", "-d", "--no-digits"])) | |||
self.assertTrue(error) | |||
self.assertTrue( | |||
"Can't have -d (--digits) and --no-digits at the same time" in message | |||
) | |||
def test_validate_args_no_opposite_rules_symbols(self): | |||
error, message = validate_args(parse_args(["site", "-s", "--no-symbols"])) | |||
self.assertTrue(error) | |||
self.assertTrue( | |||
"Can't have -s (--symbols) and --no-symbols at the same time" in message | |||
) | |||
def test_validate_args_concat_errors(self): | |||
_, message = validate_args( | |||
parse_args(["site", "-u", "--no-uppercase", "-l", "--no-lowercase"]) | |||
) | |||
self.assertTrue( | |||
"Can't have -l (--lowercase) and --no-lowercase at the same time" in message | |||
) | |||
self.assertTrue( | |||
"Can't have -u (--uppercase) and --no-uppercase at the same time" in message | |||
) | |||
def test_validate_args_no_site(self): | |||
error, message = validate_args(parse_args([])) | |||
self.assertTrue(error) | |||
self.assertTrue( | |||
"SITE is a required argument" in message | |||
) |
@@ -0,0 +1,21 @@ | |||
[tox] | |||
skipsdist = True | |||
envlist = pep8,py2,py3 | |||
[testenv] | |||
deps = -r{toxinidir}/requirements.txt | |||
-r{toxinidir}/test-requirements.txt | |||
install_command = pip install -U {packages} | |||
commands = pytest -v {posargs: tests} | |||
whitelist_externals = sh | |||
setenv = | |||
PYTHONPATH = {toxinidir} | |||
[testenv:pep8] | |||
commands = flake8 --ignore=E501 --show-source src tests | |||
[testenv:py2] | |||
basepython = python2 | |||
[testenv:py3] | |||
basepython = python3 |
@@ -1,143 +0,0 @@ | |||
#!/usr/bin/env node | |||
const clipboardy = require("clipboardy"); | |||
const meow = require("meow"); | |||
const { calcEntropy } = require("lesspass-entropy"); | |||
const renderPassword = require("lesspass-render-password"); | |||
const read = require("read"); | |||
const chalk = require("chalk"); | |||
const helpMessage = ` | |||
Usage | |||
$ lesspass <site> <login> [masterPassword] [options] | |||
Options | |||
-l add lowercase in password | |||
-u add uppercase in password | |||
-d add digits in password | |||
-s add symbols in password | |||
--no-lowercase remove lowercase from password | |||
--no-uppercase remove uppercase from password | |||
--no-digits remove digits from password | |||
--no-symbols remove symbols from password | |||
--length, -L int (default 16) | |||
--counter, -c int (default 1) | |||
--clipboard, -C copy generated password to clipboard rather than displaying it. | |||
Need pbcopy (OSX), xsel (Linux) or clip (Windows). | |||
Examples | |||
# no symbols | |||
$ lesspass lesspass.com contact@lesspass.com password --no-symbols | |||
OlfK63bmUhqrGODR | |||
# no symbols shortcut | |||
$ lesspass lesspass.com contact@lesspass.com password -lud | |||
OlfK63bmUhqrGODR | |||
# only digits and length of 8 | |||
$ lesspass lesspass.com contact@lesspass.com -d -L8 | |||
master password: | |||
75837019`; | |||
const cli = meow(helpMessage, { | |||
flags: { | |||
site: { type: "string" }, | |||
login: { type: "string" }, | |||
length: { | |||
type: "string", | |||
alias: "L" | |||
}, | |||
counter: { | |||
type: "string", | |||
alias: "c" | |||
}, | |||
clipboard: { | |||
type: "boolean", | |||
alias: "C" | |||
}, | |||
l: { type: "boolean" }, | |||
u: { type: "boolean" }, | |||
d: { type: "boolean" }, | |||
s: { type: "boolean" } | |||
} | |||
}); | |||
function calcPassword(passwordProfile, masterPassword, copyToClipboard) { | |||
calcEntropy(passwordProfile, masterPassword).then(entropy => { | |||
const generatedPassword = renderPassword(entropy, passwordProfile.options); | |||
if (copyToClipboard) { | |||
clipboardy | |||
.write(generatedPassword) | |||
.then(() => { | |||
console.log("Copied to clipboard"); | |||
process.exit(); | |||
}) | |||
.catch(err => { | |||
console.error(chalk.red("Copy failed.")); | |||
console.error(err.message); | |||
process.exit(1); | |||
}); | |||
} else { | |||
console.log(generatedPassword); | |||
process.exit(); | |||
} | |||
}); | |||
} | |||
function hasNoShortOption(options) { | |||
return !["l", "u", "d", "s"].some( | |||
shortOption => | |||
typeof options[shortOption] !== "undefined" && options[shortOption] | |||
); | |||
} | |||
function getOptionBoolean(options, optionString) { | |||
const shortOption = optionString.substring(0, 1); | |||
if (options[shortOption]) { | |||
return true; | |||
} | |||
if (typeof options[optionString] === "undefined") { | |||
return hasNoShortOption(options); | |||
} | |||
return options[optionString]; | |||
} | |||
const site = cli.input[0]; | |||
if (typeof site === "undefined") { | |||
console.log(chalk.red("site cannot be empty")); | |||
console.log("type lesspass --help"); | |||
process.exit(-1); | |||
} | |||
const lowercase = getOptionBoolean(cli.flags, "lowercase"); | |||
const uppercase = getOptionBoolean(cli.flags, "uppercase"); | |||
const symbols = getOptionBoolean(cli.flags, "symbols"); | |||
const digits = getOptionBoolean(cli.flags, "digits"); | |||
const passwordProfile = { | |||
site, | |||
login: cli.input[1] || "", | |||
options: { | |||
counter: cli.flags.counter || 1, | |||
length: cli.flags.length || 16, | |||
lowercase, | |||
uppercase, | |||
digits, | |||
symbols | |||
} | |||
}; | |||
const copyToClipboard = cli.flags.clipboard || false; | |||
if (cli.input.length === 3) { | |||
const masterPassword = cli.input[2]; | |||
calcPassword(passwordProfile, masterPassword, copyToClipboard); | |||
} else { | |||
read({ prompt: "master password: ", silent: true }, (er, password) => { | |||
if (er && er.message === "canceled") { | |||
process.exit(); | |||
} | |||
calcPassword(passwordProfile, password, copyToClipboard); | |||
}); | |||
} |
@@ -1,25 +0,0 @@ | |||
{ | |||
"name": "lesspass", | |||
"version": "9.0.0", | |||
"description": "LessPass cli", | |||
"license": "GPL-3.0", | |||
"author": { | |||
"name": "Guillaume Vincent", | |||
"email": "guillaume@oslab.fr", | |||
"url": "https://guillaumevincent.com" | |||
}, | |||
"files": [ | |||
"index.js" | |||
], | |||
"bin": { | |||
"lesspass": "index.js" | |||
}, | |||
"dependencies": { | |||
"chalk": "2.3.1", | |||
"clipboardy": "1.2.3", | |||
"lesspass-entropy": "1.0.0", | |||
"lesspass-render-password": "1.0.0", | |||
"meow": "5.0.0", | |||
"read": "1.0.7" | |||
} | |||
} |
@@ -1,294 +0,0 @@ | |||
const path = require("path"); | |||
const execa = require("execa"); | |||
const cliPath = path.resolve(__dirname, "index.js"); | |||
test("default options", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("\\g-A1-.OHEwrXjT#"); | |||
}); | |||
test("no login", async () => { | |||
const command = `echo password | ${cliPath} "lesspass.com"`; | |||
const { stdout } = await execa.shell(command); | |||
expect(stdout).toBe("master password: 7Cw-APO5Co?G>W>u"); | |||
}); | |||
test("options can be before parameters", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"-C", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("Copied to clipboard"); | |||
}); | |||
test("long options can be before parameters", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"--clipboard", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("Copied to clipboard"); | |||
}); | |||
test("length", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--length=14" | |||
]); | |||
expect(stdout).toBe("=0\\A-.OHEKvwrX"); | |||
}); | |||
test("length shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-L=14" | |||
]); | |||
expect(stdout).toBe("=0\\A-.OHEKvwrX"); | |||
}); | |||
test("counter", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--counter=2" | |||
]); | |||
expect(stdout).toBe("Vf:F1'!I`8Y2`GBE"); | |||
}); | |||
test("counter shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-c=2" | |||
]); | |||
expect(stdout).toBe("Vf:F1'!I`8Y2`GBE"); | |||
}); | |||
test("no lowercase", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--no-lowercase" | |||
]); | |||
expect(stdout).toBe('JBG\\`3{+0["(E\\JJ'); | |||
}); | |||
test("no lowercase shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-uds" | |||
]); | |||
expect(stdout).toBe('JBG\\`3{+0["(E\\JJ'); | |||
}); | |||
test("only lowercase", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-l" | |||
]); | |||
expect(stdout).toBe("fmnujoqgcxmpffyh"); | |||
}); | |||
test("no uppercase", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--no-uppercase" | |||
]); | |||
expect(stdout).toBe('jbg\\`3{+0["(e\\jj'); | |||
}); | |||
test("no uppercase shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-lds" | |||
]); | |||
expect(stdout).toBe('jbg\\`3{+0["(e\\jj'); | |||
}); | |||
test("only uppercase", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-u" | |||
]); | |||
expect(stdout).toBe("FMNUJOQGCXMPFFYH"); | |||
}); | |||
test("no digits", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--no-digits" | |||
]); | |||
expect(stdout).toBe(";zkB#m]mNF$;J_Ej"); | |||
}); | |||
test("no digits shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-lus" | |||
]); | |||
expect(stdout).toBe(";zkB#m]mNF$;J_Ej"); | |||
}); | |||
test("only digits", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-d" | |||
]); | |||
expect(stdout).toBe("7587019305478072"); | |||
}); | |||
test("no symbols", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--no-symbols" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("no symbols shortcut", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-lud" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("only symbols", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-s" | |||
]); | |||
expect(stdout).toBe("<\"]|'`%};'`>-'[,"); | |||
}); | |||
test("test space in password", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"my Master Password" | |||
]); | |||
expect(stdout).toBe("D1PBB34\\#fh!LY={"); | |||
}); | |||
test("doc 1", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"--no-symbols" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("doc 1 options before", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"--no-symbols", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("doc 2", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-lud" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("doc 2 options before", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"-lud", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("OlfK63bmUhqrGODR"); | |||
}); | |||
test("doc 3", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-d", | |||
"-L8" | |||
]); | |||
expect(stdout).toBe("75837019"); | |||
}); | |||
test("doc 3 options before", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"-d", | |||
"-L8", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password" | |||
]); | |||
expect(stdout).toBe("75837019"); | |||
}); | |||
test("doc 3 options before and after", async () => { | |||
const { stdout } = await execa(cliPath, [ | |||
"-d", | |||
"lesspass.com", | |||
"contact@lesspass.com", | |||
"password", | |||
"-L8" | |||
]); | |||
expect(stdout).toBe("75837019"); | |||
}); | |||
test("nrt numbers should be considered as string not integers", async () => { | |||
const p = execa(cliPath, ["example.org", "123", "password"]); | |||
const p2 = execa(cliPath, ["example.org", "0123", "password"]); | |||
const p3 = execa(cliPath, ["example.org", '"0123"', "password"]); | |||
const p4 = execa(cliPath, ["example.org", "00123", "password"]); | |||
return Promise.all([p, p2, p3, p4]).then(v => { | |||
expect(v[0].stdout).toBe("sMb8}N&`J4wkF9q~"); | |||
expect(v[1].stdout).toBe("5,4SqhB2[=/h\\DZh"); | |||
expect(v[2].stdout).toBe("u0Fz)EOJ4i\\{{;a~"); | |||
expect(v[3].stdout).toBe('=}|O7hN0ZHdjQ{">'); | |||
}); | |||
}); |
@@ -1,478 +0,0 @@ | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | |||
# yarn lockfile v1 | |||
ansi-styles@^3.2.0: | |||
version "3.2.1" | |||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" | |||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== | |||
dependencies: | |||
color-convert "^1.9.0" | |||
arch@^2.1.0: | |||
version "2.1.1" | |||
resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" | |||
integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== | |||
array-find-index@^1.0.1: | |||
version "1.0.2" | |||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" | |||
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= | |||
arrify@^1.0.1: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" | |||
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= | |||
builtin-modules@^1.0.0: | |||
version "1.1.1" | |||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" | |||
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= | |||
camelcase-keys@^4.0.0: | |||
version "4.2.0" | |||
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" | |||
integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= | |||
dependencies: | |||
camelcase "^4.1.0" | |||
map-obj "^2.0.0" | |||
quick-lru "^1.0.0" | |||
camelcase@^4.1.0: | |||
version "4.1.0" | |||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" | |||
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= | |||
chalk@2.3.1: | |||
version "2.3.1" | |||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" | |||
integrity sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g== | |||
dependencies: | |||
ansi-styles "^3.2.0" | |||
escape-string-regexp "^1.0.5" | |||
supports-color "^5.2.0" | |||
clipboardy@1.2.3: | |||
version "1.2.3" | |||
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef" | |||
integrity sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA== | |||
dependencies: | |||
arch "^2.1.0" | |||
execa "^0.8.0" | |||
color-convert@^1.9.0: | |||
version "1.9.3" | |||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" | |||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== | |||
dependencies: | |||
color-name "1.1.3" | |||
color-name@1.1.3: | |||
version "1.1.3" | |||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" | |||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= | |||
cross-spawn@^5.0.1: | |||
version "5.1.0" | |||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" | |||
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= | |||
dependencies: | |||
lru-cache "^4.0.1" | |||
shebang-command "^1.2.0" | |||
which "^1.2.9" | |||
currently-unhandled@^0.4.1: | |||
version "0.4.1" | |||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" | |||
integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= | |||
dependencies: | |||
array-find-index "^1.0.1" | |||
decamelize-keys@^1.0.0: | |||
version "1.1.0" | |||
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" | |||
integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= | |||
dependencies: | |||
decamelize "^1.1.0" | |||
map-obj "^1.0.0" | |||
decamelize@^1.1.0: | |||
version "1.2.0" | |||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" | |||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= | |||
error-ex@^1.3.1: | |||
version "1.3.2" | |||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" | |||
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== | |||
dependencies: | |||
is-arrayish "^0.2.1" | |||
escape-string-regexp@^1.0.5: | |||
version "1.0.5" | |||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" | |||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= | |||
execa@^0.8.0: | |||
version "0.8.0" | |||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" | |||
integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo= | |||
dependencies: | |||
cross-spawn "^5.0.1" | |||
get-stream "^3.0.0" | |||
is-stream "^1.1.0" | |||
npm-run-path "^2.0.0" | |||
p-finally "^1.0.0" | |||
signal-exit "^3.0.0" | |||
strip-eof "^1.0.0" | |||
find-up@^2.0.0: | |||
version "2.1.0" | |||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" | |||
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= | |||
dependencies: | |||
locate-path "^2.0.0" | |||
get-stream@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" | |||
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= | |||
graceful-fs@^4.1.2: | |||
version "4.1.15" | |||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" | |||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== | |||
has-flag@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" | |||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= | |||
hosted-git-info@^2.1.4: | |||
version "2.7.1" | |||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" | |||
integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== | |||
indent-string@^3.0.0: | |||
version "3.2.0" | |||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" | |||
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= | |||
is-arrayish@^0.2.1: | |||
version "0.2.1" | |||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" | |||
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= | |||
is-builtin-module@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" | |||
integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= | |||
dependencies: | |||
builtin-modules "^1.0.0" | |||
is-plain-obj@^1.1.0: | |||
version "1.1.0" | |||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" | |||
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= | |||
is-stream@^1.1.0: | |||
version "1.1.0" | |||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" | |||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= | |||
isexe@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" | |||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= | |||
json-parse-better-errors@^1.0.1: | |||
version "1.0.2" | |||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" | |||
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== | |||
load-json-file@^4.0.0: | |||
version "4.0.0" | |||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" | |||
integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= | |||
dependencies: | |||
graceful-fs "^4.1.2" | |||
parse-json "^4.0.0" | |||
pify "^3.0.0" | |||
strip-bom "^3.0.0" | |||
locate-path@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" | |||
integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= | |||
dependencies: | |||
p-locate "^2.0.0" | |||
path-exists "^3.0.0" | |||
loud-rejection@^1.0.0: | |||
version "1.6.0" | |||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" | |||
integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= | |||
dependencies: | |||
currently-unhandled "^0.4.1" | |||
signal-exit "^3.0.0" | |||
lru-cache@^4.0.1: | |||
version "4.1.5" | |||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" | |||
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== | |||
dependencies: | |||
pseudomap "^1.0.2" | |||
yallist "^2.1.2" | |||
map-obj@^1.0.0: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" | |||
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= | |||
map-obj@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" | |||
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= | |||
meow@5.0.0: | |||
version "5.0.0" | |||
resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" | |||
integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== | |||
dependencies: | |||
camelcase-keys "^4.0.0" | |||
decamelize-keys "^1.0.0" | |||
loud-rejection "^1.0.0" | |||
minimist-options "^3.0.1" | |||
normalize-package-data "^2.3.4" | |||
read-pkg-up "^3.0.0" | |||
redent "^2.0.0" | |||
trim-newlines "^2.0.0" | |||
yargs-parser "^10.0.0" | |||
minimist-options@^3.0.1: | |||
version "3.0.2" | |||
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" | |||
integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== | |||
dependencies: | |||
arrify "^1.0.1" | |||
is-plain-obj "^1.1.0" | |||
mute-stream@~0.0.4: | |||
version "0.0.7" | |||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" | |||
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= | |||
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: | |||
version "2.4.0" | |||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" | |||
integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== | |||
dependencies: | |||
hosted-git-info "^2.1.4" | |||
is-builtin-module "^1.0.0" | |||
semver "2 || 3 || 4 || 5" | |||
validate-npm-package-license "^3.0.1" | |||
npm-run-path@^2.0.0: | |||
version "2.0.2" | |||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" | |||
integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= | |||
dependencies: | |||
path-key "^2.0.0" | |||
p-finally@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" | |||
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= | |||
p-limit@^1.1.0: | |||
version "1.3.0" | |||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" | |||
integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== | |||
dependencies: | |||
p-try "^1.0.0" | |||
p-locate@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" | |||
integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= | |||
dependencies: | |||
p-limit "^1.1.0" | |||
p-try@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" | |||
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= | |||
parse-json@^4.0.0: | |||
version "4.0.0" | |||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" | |||
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= | |||
dependencies: | |||
error-ex "^1.3.1" | |||
json-parse-better-errors "^1.0.1" | |||
path-exists@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" | |||
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= | |||
path-key@^2.0.0: | |||
version "2.0.1" | |||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" | |||
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= | |||
path-type@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" | |||
integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== | |||
dependencies: | |||
pify "^3.0.0" | |||
pify@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" | |||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= | |||
pseudomap@^1.0.2: | |||
version "1.0.2" | |||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" | |||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= | |||
quick-lru@^1.0.0: | |||
version "1.1.0" | |||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" | |||
integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= | |||
read-pkg-up@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" | |||
integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= | |||
dependencies: | |||
find-up "^2.0.0" | |||
read-pkg "^3.0.0" | |||
read-pkg@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" | |||
integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= | |||
dependencies: | |||
load-json-file "^4.0.0" | |||
normalize-package-data "^2.3.2" | |||
path-type "^3.0.0" | |||
read@1.0.7: | |||
version "1.0.7" | |||
resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" | |||
integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= | |||
dependencies: | |||
mute-stream "~0.0.4" | |||
redent@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" | |||
integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= | |||
dependencies: | |||
indent-string "^3.0.0" | |||
strip-indent "^2.0.0" | |||
"semver@2 || 3 || 4 || 5": | |||
version "5.6.0" | |||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" | |||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== | |||
shebang-command@^1.2.0: | |||
version "1.2.0" | |||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" | |||
integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= | |||
dependencies: | |||
shebang-regex "^1.0.0" | |||
shebang-regex@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" | |||
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= | |||
signal-exit@^3.0.0: | |||
version "3.0.2" | |||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" | |||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= | |||
spdx-correct@^3.0.0: | |||
version "3.0.2" | |||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" | |||
integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== | |||
dependencies: | |||
spdx-expression-parse "^3.0.0" | |||
spdx-license-ids "^3.0.0" | |||
spdx-exceptions@^2.1.0: | |||
version "2.2.0" | |||
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" | |||
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== | |||
spdx-expression-parse@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" | |||
integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== | |||
dependencies: | |||
spdx-exceptions "^2.1.0" | |||
spdx-license-ids "^3.0.0" | |||
spdx-license-ids@^3.0.0: | |||
version "3.0.2" | |||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" | |||
integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== | |||
strip-bom@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" | |||
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= | |||
strip-eof@^1.0.0: | |||
version "1.0.0" | |||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" | |||
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= | |||
strip-indent@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" | |||
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= | |||
supports-color@^5.2.0: | |||
version "5.5.0" | |||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" | |||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== | |||
dependencies: | |||
has-flag "^3.0.0" | |||
trim-newlines@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" | |||
integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= | |||
validate-npm-package-license@^3.0.1: | |||
version "3.0.4" | |||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" | |||
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== | |||
dependencies: | |||
spdx-correct "^3.0.0" | |||
spdx-expression-parse "^3.0.0" | |||
which@^1.2.9: | |||
version "1.3.1" | |||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" | |||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== | |||
dependencies: | |||
isexe "^2.0.0" | |||
yallist@^2.1.2: | |||
version "2.1.2" | |||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" | |||
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= | |||
yargs-parser@^10.0.0: | |||
version "10.1.0" | |||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" | |||
integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== | |||
dependencies: | |||
camelcase "^4.1.0" |