Skip to content

Commit 9094071

Browse files
authored
Merge pull request #196 from Microsoft/dev
Merge 0.1.2 from dev
2 parents 0ea14d1 + 56d0f4b commit 9094071

File tree

97 files changed

+2503
-896
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+2503
-896
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ RUN pip install vsts --upgrade --no-cache-dir --extra-index-url https://vstscli.
4141
/vsts-cli/src/common_modules/vsts-cli-common \
4242
/vsts-cli/src/common_modules/vsts-cli-build-common \
4343
/vsts-cli/src/common_modules/vsts-cli-code-common \
44+
/vsts-cli/src/common_modules/vsts-cli-package-common \
4445
/vsts-cli/src/common_modules/vsts-cli-team-common \
4546
/vsts-cli/src/common_modules/vsts-cli-work-common \
4647
/vsts-cli/src/command_modules/vsts-cli-build \
4748
/vsts-cli/src/command_modules/vsts-cli-code \
49+
/vsts-cli/src/command_modules/vsts-cli-package \
4850
/vsts-cli/src/command_modules/vsts-cli-team \
4951
/vsts-cli/src/command_modules/vsts-cli-work \
5052
/vsts-cli/src/vsts-cli

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,14 @@ To disable telemetry use the `vsts configure` command.
117117

118118
## Feedback
119119

120-
If you encounter any bugs with the tool please report an issue on the [VSTS Developer Community](https://aka.ms/vsts-cli-devcom) using the vsts-cli tag.
120+
If you have any issues, questions, comments, or feature requests regarding this tool, please file an issue within this github repo using our contribution guidelines.
121121

122122
To find where to provide feedback from the CLI, run `vsts feedback`.
123123

124124
## Contribute
125125

126-
This project welcomes contributions and suggestions. Most contributions require you to agree to a
127-
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
128-
the rights to use your contribution. For details, visit https://cla.microsoft.com.
126+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
129127

130-
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
131-
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
132-
provided by the bot. You will only need to do this once across all repos using our CLA.
128+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
133129

134-
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
135-
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
136-
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
130+
This project follows the same contribution guidelines outlined by the Azure CLI. If you would like to become an active contributor, please follow the instructions provided in [Microsoft Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html).

packaged_releases/windows/scripts/build-packages.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,27 @@ def _build_package(path_to_package, dist_dir):
5252
def build_packages(clone_root, dist_dir):
5353
packages_to_build = [
5454
os.path.join(clone_root, 'src/common_modules/vsts-cli-common'),
55+
os.path.join(clone_root, 'src/common_modules/vsts-cli-admin-common'),
5556
os.path.join(clone_root, 'src/common_modules/vsts-cli-build-common'),
5657
os.path.join(clone_root, 'src/common_modules/vsts-cli-code-common'),
58+
os.path.join(clone_root, 'src/common_modules/vsts-cli-package-common'),
5759
os.path.join(clone_root, 'src/common_modules/vsts-cli-team-common'),
5860
os.path.join(clone_root, 'src/common_modules/vsts-cli-work-common'),
61+
os.path.join(clone_root, 'src/command_modules/vsts-cli-admin'),
5962
os.path.join(clone_root, 'src/command_modules/vsts-cli-build'),
6063
os.path.join(clone_root, 'src/command_modules/vsts-cli-code'),
64+
os.path.join(clone_root, 'src/command_modules/vsts-cli-package'),
6165
os.path.join(clone_root, 'src/command_modules/vsts-cli-team'),
6266
os.path.join(clone_root, 'src/command_modules/vsts-cli-work'),
6367
os.path.join(clone_root, 'src/vsts-cli'),
6468
]
6569

6670
for p in packages_to_build:
67-
setupPath = os.path.join(p, 'setup.py')
68-
if os.path.isfile(setupPath):
71+
setup_path = os.path.join(p, 'setup.py')
72+
if os.path.isfile(setup_path):
6973
_build_package(p, dist_dir)
7074
else:
71-
print('Failed to find file: ' + setupPath)
75+
print('Failed to find file: ' + setup_path)
7276
exit(1)
7377

7478

packaged_releases/windows/scripts/build_local.cmd

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ set "PATH=%PATH%;%ProgramFiles%\Git\bin;%ProgramFiles%\Git\usr\bin"
1010

1111
if "%CLIVERSION%"=="" (
1212
if "%BUILD_BUILDID%" == "" (
13-
set CLIVERSION=0.1.1
13+
set CLIVERSION=0.1.2
1414
) else (
15-
set CLIVERSION=0.1.1.%BUILD_BUILDID%
15+
set CLIVERSION=0.1.2.%BUILD_BUILDID%
1616
)
1717
)
1818
set PYTHON_VERSION=3.6.3
@@ -119,18 +119,26 @@ if %errorlevel% neq 0 goto ERROR
119119
if %errorlevel% neq 0 goto ERROR
120120
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-common"
121121
if %errorlevel% neq 0 goto ERROR
122+
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-admin-common"
123+
if %errorlevel% neq 0 goto ERROR
122124
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-build-common"
123125
if %errorlevel% neq 0 goto ERROR
124126
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-code-common"
125127
if %errorlevel% neq 0 goto ERROR
128+
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-package-common"
129+
if %errorlevel% neq 0 goto ERROR
126130
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-team-common"
127131
if %errorlevel% neq 0 goto ERROR
128132
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\common_modules\vsts-cli-work-common"
129133
if %errorlevel% neq 0 goto ERROR
134+
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-admin"
135+
if %errorlevel% neq 0 goto ERROR
130136
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-build"
131137
if %errorlevel% neq 0 goto ERROR
132138
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-code"
133139
if %errorlevel% neq 0 goto ERROR
140+
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-package"
141+
if %errorlevel% neq 0 goto ERROR
134142
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-team"
135143
if %errorlevel% neq 0 goto ERROR
136144
"%BUILDING_DIR%\python.exe" -m pip install -f "%TEMP_SCRATCH_FOLDER%" --no-cache-dir "%REPO_ROOT%\src\command_modules\vsts-cli-work"

scripts/docgen/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
# General information about the project.
6464
project = 'vsts-cli'
65-
copyright = '2017, msft'
65+
copyright = '2018, msft'
6666
author = 'msft'
6767

6868
# The version info for the project you're documenting, acts as replacement for

scripts/docgen/doc_source_map.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
"build": "src/command_modules/vsts-cli-build/vsts/cli/build/_help.py",
33
"code": "src/command_modules/vsts-cli-code/vsts/cli/code/_help.py",
44
"team": "src/command_modules/vsts-cli-team/vsts/cli/team/_help.py",
5-
"work": "src/command_modules/vsts-cli-work/vsts/cli/work/_help.py"
5+
"work": "src/command_modules/vsts-cli-work/vsts/cli/work/_help.py",
6+
"admin": "src/command_modules/vsts-cli-admin/vsts/cli/admin/_help.py",
7+
"package": "src/command_modules/vsts-cli-package/vsts/cli/package/_help.py"
68
}

scripts/docgen/extensions/vsts.py

Lines changed: 71 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import argparse
99
import json
1010
import os
11+
from os.path import expanduser
1112
import sys
1213

1314
from docutils import nodes
@@ -20,59 +21,34 @@
2021

2122
from knack import CLI
2223
from knack import help as _help
24+
from knack.help import GroupHelpFile, CommandHelpFile, ArgumentGroupRegistry
25+
26+
USER_HOME = expanduser('~')
2327

2428
class VstsHelpGenDirective(Directive):
2529
def make_rst(self):
2630
INDENT = ' '
2731
DOUBLEINDENT = INDENT * 2
28-
parser_keys = []
29-
parser_values = []
30-
sub_parser_keys = []
31-
sub_parser_values = []
32-
3332
# similar to what the Application object provides for in "az"
3433
cli_name = "vsts"
35-
vstscli = CLI(cli_name=cli_name,
34+
vsts_cli = CLI(cli_name=cli_name,
3635
config_dir=os.path.join('~', '.{}'.format(cli_name)),
3736
config_env_var_prefix=cli_name,
3837
commands_loader_cls=VstsCommandsLoader,
3938
help_cls=VstsCLIHelp)
4039

41-
loader = vstscli.commands_loader_cls()
42-
loader.__init__(vstscli)
43-
loader.load_command_table([])
44-
for command in loader.command_table:
45-
loader.load_arguments(command)
46-
47-
vstsclihelp = vstscli.help_cls(cli_ctx=vstscli)
40+
help_files = get_help_files(vsts_cli)
4841

49-
global_parser = vstscli.parser_cls.create_global_parser(cli_ctx=vstscli)
50-
parser = vstscli.parser_cls(cli_ctx=vstscli, prog=vstscli.name, parents=[global_parser])
51-
parser.load_command_table(loader.command_table)
52-
53-
_store_parsers(parser, parser_keys, parser_values, sub_parser_keys, sub_parser_values)
54-
for cmd, parser in zip(parser_keys, parser_values):
55-
if cmd not in sub_parser_keys:
56-
sub_parser_keys.append(cmd)
57-
sub_parser_values.append(parser)
5842
doc_source_map = _load_doc_source_map()
5943

60-
help_files = []
61-
for cmd, parser in zip(sub_parser_keys, sub_parser_values):
62-
try:
63-
help_file = _help.GroupHelpFile(cmd, parser) if _is_group(parser) else _help.CommandHelpFile(cmd, parser)
64-
help_file.load(parser)
65-
help_files.append(help_file)
66-
except Exception as ex:
67-
print("Skipped '{}' due to '{}'".format(cmd, ex))
68-
help_files = sorted(help_files, key=lambda x: x.command)
69-
7044
for help_file in help_files:
71-
is_command = isinstance(help_file, _help.CommandHelpFile)
72-
yield '.. cli{}:: {}'.format('command' if is_command else 'group', help_file.command if help_file.command else 'vsts') #it is top level group vsts if command is empty
45+
is_command = isinstance(help_file, CommandHelpFile)
46+
yield '.. cli{}:: {}'.format('command' if is_command else 'group', help_file.command if help_file.command else 'vsts') #it is top level group az if command is empty
7347
yield ''
7448
yield '{}:summary: {}'.format(INDENT, help_file.short_summary)
7549
yield '{}:description: {}'.format(INDENT, help_file.long_summary)
50+
if help_file.deprecate_info:
51+
yield '{}:deprecated: {}'.format(INDENT, help_file.deprecate_info._get_message(help_file.deprecate_info))
7652
if not is_command:
7753
top_group_name = help_file.command.split()[0] if help_file.command else 'vsts'
7854
yield '{}:docsource: {}'.format(INDENT, doc_source_map[top_group_name] if top_group_name in doc_source_map else '')
@@ -83,35 +59,46 @@ def make_rst(self):
8359
yield ''
8460

8561
if is_command and help_file.parameters:
86-
group_registry = _help.ArgumentGroupRegistry(
62+
group_registry = ArgumentGroupRegistry(
8763
[p.group_name for p in help_file.parameters if p.group_name])
8864

8965
for arg in sorted(help_file.parameters,
9066
key=lambda p: group_registry.get_group_priority(p.group_name)
9167
+ str(not p.required) + p.name):
92-
yield '{}.. cliarg:: {}'.format(INDENT, arg.name)
93-
yield ''
94-
yield '{}:required: {}'.format(DOUBLEINDENT, arg.required)
95-
short_summary = arg.short_summary or ''
96-
possible_values_index = short_summary.find(' Possible values include')
97-
short_summary = short_summary[0:possible_values_index
98-
if possible_values_index >= 0 else len(short_summary)]
99-
short_summary = short_summary.strip()
100-
yield '{}:summary: {}'.format(DOUBLEINDENT, short_summary)
101-
yield '{}:description: {}'.format(DOUBLEINDENT, arg.long_summary)
102-
if arg.choices:
103-
yield '{}:values: {}'.format(DOUBLEINDENT, ', '.join(sorted([str(x) for x in arg.choices])))
104-
if arg.default and arg.default != argparse.SUPPRESS:
105-
yield '{}:default: {}'.format(DOUBLEINDENT, arg.default)
106-
if arg.value_sources:
107-
yield '{}:source: {}'.format(DOUBLEINDENT, ', '.join(arg.value_sources))
108-
yield ''
68+
yield '{}.. cliarg:: {}'.format(INDENT, arg.name)
69+
yield ''
70+
yield '{}:required: {}'.format(DOUBLEINDENT, arg.required)
71+
if arg.deprecate_info:
72+
yield '{}:deprecated: {}'.format(DOUBLEINDENT, arg.deprecate_info._get_message(arg.deprecate_info))
73+
short_summary = arg.short_summary or ''
74+
possible_values_index = short_summary.find(' Possible values include')
75+
short_summary = short_summary[0:possible_values_index
76+
if possible_values_index >= 0 else len(short_summary)]
77+
short_summary = short_summary.strip()
78+
yield '{}:summary: {}'.format(DOUBLEINDENT, short_summary)
79+
yield '{}:description: {}'.format(DOUBLEINDENT, arg.long_summary)
80+
if arg.choices:
81+
yield '{}:values: {}'.format(DOUBLEINDENT, ', '.join(sorted([str(x) for x in arg.choices])))
82+
if arg.default and arg.default != argparse.SUPPRESS:
83+
try:
84+
if arg.default.startswith(USER_HOME):
85+
arg.default = arg.default.replace(USER_HOME, '~').replace('\\', '/')
86+
except Exception:
87+
pass
88+
try:
89+
arg.default = arg.default.replace("\\", "\\\\")
90+
except Exception:
91+
pass
92+
yield '{}:default: {}'.format(DOUBLEINDENT, arg.default)
93+
if arg.value_sources:
94+
yield '{}:source: {}'.format(DOUBLEINDENT, ', '.join(arg.value_sources))
95+
yield ''
10996
yield ''
11097
if len(help_file.examples) > 0:
11198
for e in help_file.examples:
11299
yield '{}.. cliexample:: {}'.format(INDENT, e.name)
113100
yield ''
114-
yield DOUBLEINDENT + e.text
101+
yield DOUBLEINDENT + e.text.replace("\\", "\\\\")
115102
yield ''
116103

117104
def run(self):
@@ -124,6 +111,37 @@ def run(self):
124111
nested_parse_with_titles(self.state, result, node)
125112
return node.children
126113

114+
115+
def get_help_files(cli_ctx):
116+
cli_ctx.invocation = cli_ctx.invocation_cls(cli_ctx=cli_ctx, commands_loader_cls=cli_ctx.commands_loader_cls, parser_cls=cli_ctx.parser_cls, help_cls=cli_ctx.help_cls)
117+
cli_ctx.invocation.commands_loader.load_command_table([])
118+
cmd_table = cli_ctx.invocation.commands_loader.command_table
119+
for command in cmd_table:
120+
cli_ctx.invocation.commands_loader.load_arguments(command)
121+
cli_ctx.invocation.parser.load_command_table(cli_ctx.invocation.commands_loader)
122+
123+
parser_keys = []
124+
parser_values = []
125+
sub_parser_keys = []
126+
sub_parser_values = []
127+
_store_parsers(cli_ctx.invocation.parser, parser_keys, parser_values, sub_parser_keys, sub_parser_values)
128+
for cmd, parser in zip(parser_keys, parser_values):
129+
if cmd not in sub_parser_keys:
130+
sub_parser_keys.append(cmd)
131+
sub_parser_values.append(parser)
132+
133+
help_ctx = cli_ctx.help_cls(cli_ctx=cli_ctx)
134+
help_files = []
135+
for cmd, parser in zip(sub_parser_keys, sub_parser_values):
136+
try:
137+
help_file = GroupHelpFile(help_ctx, cmd, parser) if _is_group(parser) else help_ctx.command_help_cls(help_ctx, cmd, parser)
138+
help_file.load(parser)
139+
help_files.append(help_file)
140+
except Exception as ex:
141+
print("Skipped '{}' due to '{}'".format(cmd, ex))
142+
help_files = sorted(help_files, key=lambda x: x.command)
143+
return help_files
144+
127145
def setup(app):
128146
app.add_directive('vsts', VstsHelpGenDirective)
129147

scripts/generate_command_inventory.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020

2121
cli_name = "vsts"
2222
vstscli = CLI(cli_name=cli_name,
23-
config_dir=os.path.join('~', '.{}'.format(cli_name)),
24-
config_env_var_prefix=cli_name,
25-
commands_loader_cls=VstsCommandsLoader,
26-
help_cls=VstsCLIHelp)
23+
config_dir=os.path.join('~', '.{}'.format(cli_name)),
24+
config_env_var_prefix=cli_name,
25+
commands_loader_cls=VstsCommandsLoader,
26+
help_cls=VstsCLIHelp)
2727

2828
loader = vstscli.commands_loader_cls()
2929
loader.__init__(vstscli)

scripts/windows/init.cmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ if not "%~2" == "" (
4040

4141
IF NOT EXIST "%VDIR%" (
4242
echo Creating new virtual environment under %VDIR%
43+
"%PYTHONEXE%" -m pip install virtualenv
4344
"%PYTHONEXE%" -m venv "%VDIR%"
4445
if ERRORLEVEL 1 (
4546
if not "%PYTHONDIR%" == "" (

scripts/windows/macros.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ fjs=findstr /ips /c:"$*" *.js
4141
fts=findstr /ips /c:"$*" *.ts
4242
fproj=findstr /ips /c:"$*" *.*proj
4343

44+
;= ********************************************************************************
45+
;= * Testing
46+
;= ********************************************************************************
47+
ut=run_unit_tests.cmd
48+
l0=run_unit_tests.cmd
49+
4450
;= ********************************************************************************
4551
;= * Please leave the following line in place, as it is part of the commenting
4652
;= * infrastructure in this file.

scripts/windows/run_unit_tests.cmd

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@REM init section. Set _echo=1 to echo everything
2+
@IF NOT DEFINED _echo ECHO OFF
3+
4+
IF EXIST "%BUILD_BINARIESDIRECTORY%\python.3.6.2\tools\python.exe" (
5+
REM Build step installs Python here.
6+
SET PYTHONEXE=%BUILD_BINARIESDIRECTORY%\python.3.6.2\tools\python.exe
7+
) ELSE (
8+
SET PYTHONEXE=python.exe
9+
)
10+
11+
pushd "%~dp0\..\..\src\common_modules"
12+
"%PYTHONEXE%" -m unittest discover -s vsts-cli-common -v
13+
IF ERRORLEVEL 1 GOTO FAIL
14+
"%PYTHONEXE%" -m unittest discover -s vsts-cli-team-common -v
15+
IF ERRORLEVEL 1 GOTO FAIL
16+
popd
17+
pushd "%~dp0\..\..\src"
18+
"%PYTHONEXE%" -m unittest discover -s vsts-cli -v
19+
IF ERRORLEVEL 1 GOTO FAIL
20+
popd
21+
22+
23+
SET PYTHONEXE=
24+
25+
GOTO :EOF
26+
27+
:FAIL
28+
popd
29+
ECHO Unit test run failed.
30+
EXIT /B 1
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Copyright (c) Microsoft Corporation. All rights reserved.
2+
3+
MIT License
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)