-
Notifications
You must be signed in to change notification settings - Fork 46
/
run.py
183 lines (148 loc) · 7.12 KB
/
run.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import logging
from functools import cache
from pathlib import Path
import click
from algokit.cli.common.utils import MutuallyExclusiveOption
from algokit.core.project import ProjectType
from algokit.core.project.run import (
ProjectCommand,
WorkspaceProjectCommand,
load_commands,
run_command,
run_workspace_command,
)
logger = logging.getLogger(__name__)
@cache
def _load_project_commands(project_dir: Path) -> dict[str, click.Command]:
"""
Loads project commands from the .algokit.toml file located in the specified project directory.
This function reads the project directory's .algokit.toml configuration file, extracts custom commands defined
within it, and returns a dictionary mapping command names to their corresponding Click command objects.
Args:
project_dir (Path): The path to the project directory.
Returns:
dict[str, click.Command]: A dictionary where keys are command names and values are Click command objects.
"""
custom_commands = load_commands(project_dir)
if custom_commands is None:
return {}
commands_table: dict[str, click.Command] = {}
for custom_command in custom_commands:
# Define the base command function
def base_command(
*,
args: list[str],
custom_command: ProjectCommand | WorkspaceProjectCommand = custom_command,
project_names: tuple[str] | None = None,
list_projects: bool = False,
project_type: str | None = None,
) -> None:
"""
Executes a base command function with optional parameters for listing projects or specifying project names.
This function serves as the base for executing both ProjectCommand and WorkspaceProjectCommand instances.
It handles listing projects within a workspace and executing commands for specific projects or all projects
within a workspace.
Args:
args (list[str]): The command arguments to be passed to the custom command.
custom_command (ProjectCommand | WorkspaceProjectCommand): The custom command to be executed.
project_names (list[str] | None): Optional. A list of project names to execute the command on.
list_projects (bool): Optional. A flag indicating whether to list projects associated
with a workspace command.
project_type (str | None): Optional. Only execute commands in projects of specified type.
Returns:
None
"""
if args:
logger.warning("Ignoring unrecognized arguments: %s.", " ".join(args))
if list_projects and isinstance(custom_command, WorkspaceProjectCommand):
for command in custom_command.commands:
cmds = " && ".join(" ".join(cmd) for cmd in command.commands)
logger.info(f"ℹ️ Project: {command.project_name}, Command name: {command.name}, Command(s): {cmds}") # noqa: RUF001
return
run_command(command=custom_command) if isinstance(
custom_command, ProjectCommand
) else run_workspace_command(custom_command, list(project_names or []), project_type)
# Check if the command is a WorkspaceProjectCommand and conditionally decorate
is_workspace_command = isinstance(custom_command, WorkspaceProjectCommand)
command = click.argument("args", nargs=-1, type=click.UNPROCESSED, required=False)(base_command)
if is_workspace_command:
command = click.option(
"project_names",
"--project-name",
"-p",
multiple=True,
help=(
"Optional. Execute the command on specified projects. "
"Defaults to all projects in the current directory."
),
nargs=1,
default=[],
required=False,
)(base_command)
command = click.option(
"list_projects",
"--list",
"-l",
help="(Optional) List all projects associated with workspace command",
default=False,
is_flag=True,
type=click.BOOL,
required=False,
cls=MutuallyExclusiveOption,
not_required_if=["project_names"],
)(command)
command = click.option(
"project_type",
"--type",
"-t",
type=click.Choice([ProjectType.FRONTEND, ProjectType.CONTRACT, ProjectType.BACKEND]),
required=False,
default=None,
help="Limit execution to specific project types if executing from workspace. (Optional)",
)(command)
# Apply the click.command decorator with common options
command = click.command(
name=custom_command.name,
help=f"{custom_command.description}" or "Command description is not supplied.",
context_settings={
# Enables workspace commands in standalone projects without execution impact,
# supporting uniform GitHub workflows across official templates.
"ignore_unknown_options": not is_workspace_command,
},
)(command)
commands_table[custom_command.name] = command
return commands_table
class RunCommandGroup(click.Group):
"""
A custom Click command group for dynamically loading and executing project commands.
This command group overrides the default Click command loading mechanism to include dynamically loaded project
commands from the .algokit.toml configuration file. It supports both predefined and dynamically loaded commands.
"""
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
"""
Retrieves a command by name, including dynamically loaded project commands.
Args:
ctx (click.Context): The current Click context.
cmd_name (str): The name of the command to retrieve.
Returns:
click.Command | None: The requested command if found; otherwise, None.
"""
return_value = super().get_command(ctx, cmd_name)
if return_value is not None:
return return_value
return _load_project_commands(Path.cwd()).get(cmd_name)
def list_commands(self, ctx: click.Context) -> list[str]:
"""
Lists all available commands, including dynamically loaded project commands.
Args:
ctx (click.Context): The current Click context.
Returns:
list[str]: A sorted list of all available command names.
"""
predefined_command_names = super().list_commands(ctx)
dynamic_commands = _load_project_commands(Path.cwd())
dynamic_command_names = list(dynamic_commands)
return sorted(predefined_command_names + dynamic_command_names)
@click.group("run", cls=RunCommandGroup)
def run_group() -> None:
"""Define custom commands and manage their execution in you projects."""