bake 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bake/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Bake package."""
bake/cli.py ADDED
@@ -0,0 +1,40 @@
1
+ import sys
2
+
3
+ from dotenv import load_dotenv
4
+
5
+ from bake import dpkg, install
6
+
7
+ load_dotenv()
8
+
9
+
10
+ def main():
11
+ if len(sys.argv) < 2:
12
+ print("Error: No command provided")
13
+ print("Usage: bake <command> [args...]")
14
+ print("")
15
+ print("Available commands:")
16
+ print("")
17
+ print(" install Initialize Bake")
18
+ print(" dpkg Bake package manager")
19
+ print("")
20
+ sys.exit(1)
21
+
22
+ command = sys.argv[1]
23
+ command_args = sys.argv[2:]
24
+ original_argv = sys.argv
25
+ sys.argv = ["bake " + command] + command_args
26
+
27
+ try:
28
+ if command == "install":
29
+ install.main()
30
+ elif command == "dpkg":
31
+ dpkg.main()
32
+ else:
33
+ print(f"Error: Unknown command '{command}'")
34
+ sys.exit(1)
35
+ finally:
36
+ sys.argv = original_argv
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
@@ -0,0 +1 @@
1
+ """Bake command modules."""
@@ -0,0 +1 @@
1
+ """Package management command modules for Bake."""
@@ -0,0 +1,45 @@
1
+ from pathlib import Path
2
+
3
+ from bake.exceptions import BakePathNotConfiguredException, ErrorCopyingException
4
+ from bake.output_style import OutputStyle
5
+ from bake.system.copy_subprocess import CopySubprocess
6
+ from bake.utils.bake_setup import BakeSetup
7
+
8
+
9
+ class BakeDpkgCommand:
10
+ def __init__(self, output_style: OutputStyle, bake_setup: BakeSetup):
11
+ self.output_style = output_style
12
+ self.bake_setup = bake_setup
13
+
14
+ def sync_configurations(self):
15
+ """Synchronize default bake definitions into BAKE_PATH."""
16
+ try:
17
+ bake_path = self.bake_setup.bake_path()
18
+ except BakePathNotConfiguredException:
19
+ self.output_style.writeln("No Bake path set", "error")
20
+ return
21
+
22
+ built_in_tools_dir = self._built_in_tools_dir()
23
+ try:
24
+ CopySubprocess().copy(
25
+ str(built_in_tools_dir), bake_path + "/", ["-Rf"]
26
+ )
27
+ except ErrorCopyingException as exc:
28
+ self.output_style.writeln(
29
+ "Error copying default configuration files: " + str(exc), "error"
30
+ )
31
+ return
32
+
33
+ self.output_style.writeln(
34
+ "Built-in tools directory copied successfully.", "success"
35
+ )
36
+
37
+ def _built_in_tools_dir(self) -> Path:
38
+ built_in_tools_dir = Path(__file__).resolve().parents[3] / "tools" / "config"
39
+ if not built_in_tools_dir.exists():
40
+ raise MissingBuiltInToolsDirException(
41
+ "No built-in tools directory found at "
42
+ + str(built_in_tools_dir.absolute())
43
+ )
44
+
45
+ return built_in_tools_dir
@@ -0,0 +1 @@
1
+ """Initialization command modules for Bake."""
@@ -0,0 +1,52 @@
1
+ import json
2
+
3
+ from bake.exceptions import (
4
+ BakeAlreadyExistsException,
5
+ ErrorReadingBakeConfigException,
6
+ MissingConfigurationKeysException,
7
+ )
8
+ from bake.output_style import OutputStyle
9
+ from bake.system.git_subprocess import GitSubprocess
10
+ from bake.utils.bake_setup import BakeSetup
11
+
12
+
13
+ class BakeDownloader:
14
+ """Download Bake-managed repositories from config definitions."""
15
+
16
+ def __init__(self):
17
+ self.output = OutputStyle()
18
+ self.git_subprocess = GitSubprocess()
19
+ self.bake_setup = BakeSetup()
20
+
21
+ def list_available_bakes(self):
22
+ bake_list = []
23
+ for config_file in self.bake_setup.config_path().glob("*.json"):
24
+ try:
25
+ with open(config_file, "r") as config_stream:
26
+ config = json.load(config_stream)
27
+ bake_list.append(config)
28
+ except Exception as exc:
29
+ raise ErrorReadingBakeConfigException(
30
+ "Error reading bake configuration "
31
+ + str(config_file.absolute())
32
+ + ": "
33
+ + str(exc)
34
+ ) from exc
35
+ return bake_list
36
+
37
+ def download_bake(self, config):
38
+ name = config.get("name")
39
+ repo_url = config.get("repo_url")
40
+
41
+ if not name or not repo_url:
42
+ raise MissingConfigurationKeysException(
43
+ "Invalid configuration: missing name or repo_url"
44
+ )
45
+
46
+ target_path = self.bake_setup.repositories_path() / name
47
+ if target_path.exists():
48
+ raise BakeAlreadyExistsException(
49
+ "Bake " + name + " already exists at " + str(target_path.absolute())
50
+ )
51
+
52
+ return self.git_subprocess.clone(repo_url, target_path)
@@ -0,0 +1,217 @@
1
+ from pathlib import Path
2
+
3
+ from bake.commands.init.bake_downloader import BakeDownloader
4
+ from bake.commands.init.bake_installer import BakeInstaller
5
+ from bake.commands.init.bake_path import BakePath
6
+ from bake.exceptions import (
7
+ BakeAlreadyExistsException,
8
+ BakePathNotConfiguredException,
9
+ ErrorCopyingException,
10
+ ErrorReadingBakeConfigException,
11
+ MissingConfigurationKeysException,
12
+ NoInstallableBakesException,
13
+ )
14
+ from bake.output_style import OutputStyle
15
+ from bake.system.copy_subprocess import CopySubprocess
16
+ from bake.utils.bake_setup import BakeSetup
17
+
18
+
19
+ class BakeInitCommand:
20
+ def __init__(self, bake_path: BakePath):
21
+ self.bake_path = bake_path
22
+ self.output = OutputStyle()
23
+ self.bake_setup = BakeSetup()
24
+
25
+ def execute(self):
26
+ self.set_bake_path()
27
+ self.setup_bake_directories()
28
+ self.download_bakes()
29
+ self.install_bakes()
30
+
31
+ def set_bake_path(self):
32
+ self.output.writeln("Initializing Bake...", "success")
33
+ self.output.writeln()
34
+
35
+ try:
36
+ current_path = self.bake_setup.bake_path()
37
+ except BakePathNotConfiguredException:
38
+ current_path = None
39
+
40
+ if current_path:
41
+ self.output.writeln(f"Current Bake path: {current_path}", "info")
42
+ else:
43
+ self.output.writeln("No Bake path configured yet.", "warning")
44
+ self.output.writeln("Suggested paths:")
45
+ for idx, path in enumerate(self.bake_path.get_suggested_paths(), 1):
46
+ self.output.writeln(f" {idx}. {path}", "info")
47
+
48
+ while True:
49
+ if current_path:
50
+ self.output.writeln("Press Enter to keep this path or enter a new one:")
51
+ else:
52
+ self.output.writeln("Enter the path where Bake repositories will live:")
53
+
54
+ new_path = input().strip()
55
+ final_path = new_path if new_path else current_path
56
+ if final_path:
57
+ break
58
+
59
+ self.output.writeln("Invalid path. Please try again.", "error")
60
+
61
+ if self.bake_path.path_exists(final_path):
62
+ self.output.writeln(f"Directory already exists: {final_path}", "success")
63
+ else:
64
+ success, message = self.bake_path.create_directory(final_path)
65
+ self.output.writeln(message, "success" if success else "error")
66
+ if not success:
67
+ return
68
+
69
+ self.bake_path.save_env_file("BAKE_PATH", final_path)
70
+ self.output.writeln(f"Bake path configured: {final_path}", "success")
71
+
72
+ def setup_bake_directories(self):
73
+ try:
74
+ bake_path = self.bake_setup.bake_path()
75
+ except BakePathNotConfiguredException:
76
+ self.output.writeln("Bake path not configured", "error")
77
+ return
78
+
79
+ self.output.writeln()
80
+ self.output.writeln("Setting up Bake directories...", "info")
81
+
82
+ if not self.bake_setup.config_dir_exists() or self.bake_setup.is_config_dir_empty():
83
+ self.bake_setup.setup_config_dir()
84
+ self.output.writeln("Config directory created", "success")
85
+
86
+ built_in_tools_dir = self._built_in_tools_dir()
87
+ try:
88
+ CopySubprocess().copy(
89
+ str(built_in_tools_dir), bake_path + "/", ["-R"]
90
+ )
91
+ except ErrorCopyingException as exc:
92
+ self.output.writeln(
93
+ "Error copying built-in tools: " + str(exc), "error"
94
+ )
95
+ return
96
+
97
+ self.output.writeln("Built-in tools directory copied", "success")
98
+
99
+ if not self.bake_setup.repositories_dir_exists():
100
+ self.bake_setup.setup_repositories_dir()
101
+ self.output.writeln("Repositories directory created", "success")
102
+
103
+ if not self.bake_setup.bin_dir_exists():
104
+ self.bake_setup.setup_bin_dir()
105
+ self.output.writeln("Bin directory created", "success")
106
+
107
+ def download_bakes(self):
108
+ try:
109
+ self.bake_setup.bake_path()
110
+ except BakePathNotConfiguredException:
111
+ self.output.writeln("Bake path not configured", "error")
112
+ return
113
+
114
+ downloader = BakeDownloader()
115
+ try:
116
+ bakes = downloader.list_available_bakes()
117
+ self.output.writeln()
118
+ self.output.writeln(f"Available bakes ({len(bakes)}):", "info")
119
+ for idx, config in enumerate(bakes, 1):
120
+ self.output.writeln(f" {idx}. {config.get('name', 'Unknown')}", "info")
121
+ self.output.writeln(
122
+ f" Description: {config.get('description', 'No description')}",
123
+ "info",
124
+ )
125
+ self.output.writeln(
126
+ f" Repository: {config.get('repo_url', 'No URL')}", "info"
127
+ )
128
+ except ErrorReadingBakeConfigException as exc:
129
+ self.output.writeln("Error listing available bakes: " + str(exc), "error")
130
+ return
131
+
132
+ self.output.writeln()
133
+ self.output.writeln("Download available bakes? (y/n):", "info")
134
+ if input().strip().lower() not in ["y", "yes"]:
135
+ return
136
+
137
+ self.output.writeln()
138
+ success_count = 0
139
+ for config in bakes:
140
+ name = config.get("name", "Unknown")
141
+ self.output.writeln(f"Processing: {name}", "info")
142
+ try:
143
+ downloader.download_bake(config)
144
+ success_count += 1
145
+ except BakeAlreadyExistsException:
146
+ self.output.writeln(f"{name} already exists", "info")
147
+ except MissingConfigurationKeysException as exc:
148
+ self.output.writeln(f"Error downloading {name}: {exc}", "error")
149
+
150
+ self.output.writeln()
151
+ self.output.writeln(
152
+ f"Successfully downloaded {success_count}/{len(bakes)} bakes", "success"
153
+ )
154
+
155
+ def install_bakes(self):
156
+ try:
157
+ self.bake_setup.bake_path()
158
+ except BakePathNotConfiguredException:
159
+ self.output.writeln("Bake path not configured", "error")
160
+ return
161
+
162
+ installer = BakeInstaller()
163
+ try:
164
+ installable_bakes = installer.list_installable_bakes()
165
+ self.output.writeln()
166
+ self.output.writeln(
167
+ f"Installable bakes ({len(installable_bakes)}):", "info"
168
+ )
169
+ for idx, bake_name in enumerate(installable_bakes, 1):
170
+ self.output.writeln(f" {idx}. {bake_name}", "info")
171
+ except NoInstallableBakesException as exc:
172
+ self.output.writeln(str(exc), "error")
173
+ return
174
+
175
+ self.output.writeln()
176
+ self.output.writeln("Install available bakes? (y/n):", "info")
177
+ if input().strip().lower() not in ["y", "yes"]:
178
+ return
179
+
180
+ self.output.writeln()
181
+ self.output.writeln("Installing bakes...", "info")
182
+ results = installer.install_all_bakes()
183
+ success_count = 0
184
+ failed_count = 0
185
+
186
+ for bake_name, result in results.items():
187
+ if result["success"]:
188
+ success_count += 1
189
+ self.output.writeln(f"{bake_name}: installed", "success")
190
+ else:
191
+ failed_count += 1
192
+ self.output.writeln(f"{bake_name}: failed", "error")
193
+ self.output.writeln(f" Error: {result.get('error', 'Unknown')}", "error")
194
+
195
+ self.output.writeln()
196
+ if failed_count == 0:
197
+ self.output.writeln("Bake initialization completed successfully!", "success")
198
+ self.output.writeln("Add this to your shell profile:", "info")
199
+ self.output.writeln(
200
+ f' export PATH="$PATH:{self.bake_setup.bin_path()}"', "warning"
201
+ )
202
+ else:
203
+ self.output.writeln(
204
+ f"{success_count} bakes installed successfully, {failed_count} failed.",
205
+ "warning",
206
+ )
207
+
208
+ def _built_in_tools_dir(self) -> Path:
209
+ # First look for packaged configs (works from wheel/sdist installs).
210
+ built_in_tools_dir = Path(__file__).resolve().parents[3] / "tools" / "config"
211
+ if not built_in_tools_dir.exists():
212
+ raise MissingBuiltInToolsDirException(
213
+ "No built-in tools directory found at "
214
+ + str(built_in_tools_dir.absolute())
215
+ )
216
+
217
+ return built_in_tools_dir
@@ -0,0 +1,234 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+
5
+ from bake.exceptions import (
6
+ BakeNotFoundException,
7
+ ErrorCreatingSymlinkException,
8
+ ErrorReadingBakeConfigException,
9
+ NoInstallableBakesException,
10
+ SetupCommandFailedException,
11
+ )
12
+ from bake.utils.bake_setup import BakeSetup
13
+
14
+
15
+ class BakeInstaller:
16
+ """Install Bake-managed repositories and expose runnable binaries."""
17
+
18
+ def __init__(self):
19
+ self.bake_setup = BakeSetup()
20
+
21
+ def get_installed_bakes(self):
22
+ if not self.bake_setup.repositories_dir_exists():
23
+ return []
24
+ return [
25
+ d.name
26
+ for d in self.bake_setup.repositories_path().iterdir()
27
+ if d.is_dir()
28
+ ]
29
+
30
+ def load_bake_config(self, bake_name):
31
+ config_file = self.bake_setup.config_path() / f"{bake_name}.json"
32
+ if not config_file.exists():
33
+ raise ErrorReadingBakeConfigException(
34
+ f"Configuration file not found: {config_file}"
35
+ )
36
+
37
+ try:
38
+ with open(config_file, "r") as config_stream:
39
+ return json.load(config_stream)
40
+ except json.JSONDecodeError as exc:
41
+ raise ErrorReadingBakeConfigException(
42
+ f"Invalid JSON in {config_file}: {exc}"
43
+ ) from exc
44
+ except Exception as exc:
45
+ raise ErrorReadingBakeConfigException(
46
+ f"Error reading {config_file}: {exc}"
47
+ ) from exc
48
+
49
+ def run_setup_script(self, bake_name, script_commands):
50
+ bake_path = self.bake_setup.repositories_path() / bake_name
51
+ if not bake_path.exists():
52
+ raise BakeNotFoundException(f"Bake {bake_name} not found at {bake_path}")
53
+
54
+ original_dir = os.getcwd()
55
+ os.chdir(bake_path)
56
+ try:
57
+ results = []
58
+ for command in script_commands:
59
+ if command.startswith("source"):
60
+ result = subprocess.run(
61
+ ["bash", "-c", command], capture_output=True, text=True
62
+ )
63
+ else:
64
+ result = subprocess.run(
65
+ command.split(), capture_output=True, text=True
66
+ )
67
+
68
+ output = result.stdout or result.stderr or ""
69
+ results.append(
70
+ {"command": command, "code": result.returncode, "output": output}
71
+ )
72
+
73
+ if result.returncode != 0:
74
+ raise SetupCommandFailedException(
75
+ f"Command failed: {command}. Error: {result.stderr}"
76
+ )
77
+
78
+ return results
79
+ except SetupCommandFailedException:
80
+ raise
81
+ except Exception as exc:
82
+ raise SetupCommandFailedException(
83
+ f"Error running setup script for {bake_name}: {exc}"
84
+ ) from exc
85
+ finally:
86
+ os.chdir(original_dir)
87
+
88
+ def create_binary_symlinks(self, bake_name, binaries):
89
+ if not binaries:
90
+ return []
91
+
92
+ bake_path = self.bake_setup.repositories_path() / bake_name
93
+ bin_path = self.bake_setup.bin_path()
94
+
95
+ if not bake_path.exists():
96
+ raise BakeNotFoundException(f"Bake {bake_name} not found at {bake_path}")
97
+ if not bin_path.exists():
98
+ raise ErrorCreatingSymlinkException(f"Bin directory not found at {bin_path}")
99
+
100
+ created_links = []
101
+ for binary_config in binaries:
102
+ if isinstance(binary_config, str):
103
+ binary_path = binary_config
104
+ alias = (
105
+ binary_path.replace(".py", "")
106
+ if binary_path.endswith(".py")
107
+ else binary_path
108
+ )
109
+ chdir = False
110
+ executor = self._get_default_executor(binary_path)
111
+ elif isinstance(binary_config, dict):
112
+ binary_path = binary_config.get("path")
113
+ alias = binary_config.get("alias")
114
+ chdir = binary_config.get("chdir", False)
115
+ executor = binary_config.get(
116
+ "executor", self._get_default_executor(binary_path)
117
+ )
118
+ if not binary_path:
119
+ raise ErrorCreatingSymlinkException(
120
+ f"Binary configuration missing 'path': {binary_config}"
121
+ )
122
+ if not alias:
123
+ alias = (
124
+ binary_path.replace(".py", "")
125
+ if binary_path.endswith(".py")
126
+ else binary_path
127
+ )
128
+ else:
129
+ raise ErrorCreatingSymlinkException(
130
+ f"Invalid binary configuration format: {binary_config}"
131
+ )
132
+
133
+ source_path = bake_path / binary_path
134
+ if not source_path.exists():
135
+ raise ErrorCreatingSymlinkException(
136
+ f"Binary {binary_path} not found in {bake_name} at {source_path}"
137
+ )
138
+
139
+ link_path = bin_path / alias
140
+ try:
141
+ if link_path.exists() or link_path.is_symlink():
142
+ link_path.unlink()
143
+
144
+ if chdir:
145
+ self._create_chdir_wrapper(bake_path, source_path, link_path, executor)
146
+ else:
147
+ link_path.symlink_to(source_path)
148
+ created_links.append(alias)
149
+ except Exception as exc:
150
+ raise ErrorCreatingSymlinkException(
151
+ f"Error creating symbolic link for {binary_path} as {alias}: {exc}"
152
+ ) from exc
153
+
154
+ return created_links
155
+
156
+ def _create_chdir_wrapper(self, bake_path, source_path, link_path, executor):
157
+ if executor == "uv run":
158
+ exec_command = f'exec uv run "{source_path}" "$@"'
159
+ elif executor == "source":
160
+ exec_command = f'. "{source_path}" "$@"'
161
+ elif executor == "direct":
162
+ exec_command = f'exec "{source_path}" "$@"'
163
+ else:
164
+ exec_command = f'exec uv run "{source_path}" "$@"'
165
+
166
+ wrapper_content = f"""#!/usr/bin/env bash
167
+ # Wrapper script for {source_path.name}
168
+ # Ensures execution in repository directory.
169
+
170
+ cd "{bake_path}"
171
+ {exec_command}
172
+ """
173
+ with open(link_path, "w") as wrapper:
174
+ wrapper.write(wrapper_content)
175
+ link_path.chmod(0o755)
176
+
177
+ def _get_default_executor(self, binary_path):
178
+ if binary_path.endswith(".py"):
179
+ return "uv run"
180
+ if binary_path.endswith(".sh"):
181
+ return "source"
182
+ return "direct"
183
+
184
+ def install_bake(self, bake_name):
185
+ try:
186
+ config = self.load_bake_config(bake_name)
187
+ scripts = config.get("scripts", {})
188
+ install_scripts = scripts.get("install", [])
189
+ if not install_scripts:
190
+ install_scripts = config.get(
191
+ "installation_scripts", config.get("setup_scripts", [])
192
+ )
193
+ binaries = config.get("binaries", [])
194
+
195
+ results = {"setup": None, "symlinks": []}
196
+
197
+ if install_scripts:
198
+ results["setup"] = self.run_setup_script(bake_name, install_scripts)
199
+ elif not binaries:
200
+ raise SetupCommandFailedException(
201
+ f"No installation scripts or binaries defined for {bake_name}"
202
+ )
203
+
204
+ if binaries:
205
+ results["symlinks"] = self.create_binary_symlinks(bake_name, binaries)
206
+ return results
207
+ except ErrorReadingBakeConfigException as exc:
208
+ raise SetupCommandFailedException(
209
+ f"Error loading configuration for {bake_name}: {exc}"
210
+ ) from exc
211
+
212
+ def install_all_bakes(self):
213
+ installed_bakes = self.get_installed_bakes()
214
+ if not installed_bakes:
215
+ raise NoInstallableBakesException("No bakes found to install")
216
+
217
+ results = {}
218
+ for bake_name in installed_bakes:
219
+ try:
220
+ result = self.install_bake(bake_name)
221
+ results[bake_name] = {"success": True, "result": result}
222
+ except (
223
+ BakeNotFoundException,
224
+ SetupCommandFailedException,
225
+ ErrorReadingBakeConfigException,
226
+ ) as exc:
227
+ results[bake_name] = {"success": False, "error": str(exc)}
228
+ return results
229
+
230
+ def list_installable_bakes(self):
231
+ installed_bakes = self.get_installed_bakes()
232
+ if not installed_bakes:
233
+ raise NoInstallableBakesException("No bakes found to install")
234
+ return installed_bakes
@@ -0,0 +1,58 @@
1
+ import os
2
+ import platform
3
+ from pathlib import Path
4
+
5
+
6
+ class BakePath:
7
+ """Path helpers for Bake home directory configuration."""
8
+
9
+ def get_default_path(self):
10
+ return os.getenv("BAKE_PATH", "")
11
+
12
+ def get_suggested_paths(self):
13
+ home = Path.home()
14
+ common_paths = [
15
+ str(home / "bake"),
16
+ str(home / ".bake"),
17
+ str(home / "Documents" / "bake"),
18
+ str(home / "Desktop" / "bake"),
19
+ ]
20
+
21
+ if platform.system().lower() == "linux":
22
+ return common_paths + [
23
+ str(home / ".local" / "share" / "bake"),
24
+ str(home / ".config" / "bake"),
25
+ ]
26
+
27
+ return common_paths
28
+
29
+ def path_exists(self, path_str):
30
+ return Path(path_str).exists()
31
+
32
+ def create_directory(self, path_str):
33
+ path_obj = Path(path_str)
34
+ try:
35
+ path_obj.mkdir(parents=True, exist_ok=True)
36
+ return True, f"Created directory: {path_str}"
37
+ except PermissionError:
38
+ return False, f"Permission denied creating directory: {path_str}"
39
+ except OSError as exc:
40
+ return False, f"Error creating directory: {exc}"
41
+
42
+ def save_env_file(self, key, value):
43
+ env_file = Path(".env")
44
+ existing_vars = {}
45
+
46
+ if env_file.exists():
47
+ with open(env_file, "r") as env:
48
+ for line in env:
49
+ line = line.strip()
50
+ if line and not line.startswith("#") and "=" in line:
51
+ k, v = line.split("=", 1)
52
+ existing_vars[k] = v
53
+
54
+ existing_vars[key] = value
55
+
56
+ with open(env_file, "w") as env:
57
+ for k, v in existing_vars.items():
58
+ env.write(f"{k}={v}\n")
bake/dpkg.py ADDED
@@ -0,0 +1,32 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from bake.commands.dpkg.bake_dpkg_command import BakeDpkgCommand
5
+ from bake.output_style import OutputStyle
6
+ from bake.utils.bake_setup import BakeSetup
7
+
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(
11
+ description="Bake package manager commands",
12
+ formatter_class=argparse.RawDescriptionHelpFormatter,
13
+ )
14
+ parser.add_argument("command", help="Command to execute")
15
+ args = parser.parse_args()
16
+
17
+ if args.command == "sync-config":
18
+ BakeDpkgCommand(OutputStyle(), BakeSetup()).sync_configurations()
19
+ return
20
+
21
+ print("Error: Invalid command provided")
22
+ print("Usage: bake dpkg <command>")
23
+ print("")
24
+ print("Available commands:")
25
+ print("")
26
+ print(" sync-config Synchronize bake definitions")
27
+ print("")
28
+ sys.exit(1)
29
+
30
+
31
+ if __name__ == "__main__":
32
+ main()
bake/exceptions.py ADDED
@@ -0,0 +1,56 @@
1
+ class BakePathNotConfiguredException(Exception):
2
+ pass
3
+
4
+
5
+ class BakeAlreadyExistsException(Exception):
6
+ pass
7
+
8
+
9
+ class BakeNotFoundException(Exception):
10
+ pass
11
+
12
+
13
+ class MissingConfigurationKeysException(Exception):
14
+ pass
15
+
16
+
17
+ class ErrorReadingBakeConfigException(Exception):
18
+ pass
19
+
20
+
21
+ class SetupCommandFailedException(Exception):
22
+ pass
23
+
24
+
25
+ class NoInstallableBakesException(Exception):
26
+ pass
27
+
28
+
29
+ class ErrorCreatingSymlinkException(Exception):
30
+ pass
31
+
32
+
33
+ class ErrorCopyingException(Exception):
34
+ pass
35
+
36
+ class MissingBuiltInToolsDirException(Exception):
37
+ pass
38
+
39
+ class ErrorCreatingConfigFolderException(Exception):
40
+ pass
41
+
42
+
43
+ class ErrorCreatingRepoFolderException(Exception):
44
+ pass
45
+
46
+
47
+ class NoConfigurationsFolderException(Exception):
48
+ pass
49
+
50
+
51
+ class MissingPackageException(Exception):
52
+ pass
53
+
54
+
55
+ class ErrorCloningRepositoryException(Exception):
56
+ pass
bake/install.py ADDED
@@ -0,0 +1,10 @@
1
+ from bake.commands.init.bake_init_command import BakeInitCommand
2
+ from bake.commands.init.bake_path import BakePath
3
+
4
+
5
+ def main():
6
+ BakeInitCommand(BakePath()).execute()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()
bake/output_style.py ADDED
@@ -0,0 +1,24 @@
1
+ from rich.console import Console
2
+ from rich.theme import Theme
3
+
4
+
5
+ class OutputStyle:
6
+ """Styled terminal output for Bake."""
7
+
8
+ def __init__(self):
9
+ self.console = Console(
10
+ theme=Theme(
11
+ {
12
+ "info": "cyan",
13
+ "warning": "yellow",
14
+ "error": "red bold",
15
+ "success": "green",
16
+ }
17
+ )
18
+ )
19
+
20
+ def write(self, message: str, style: str = "") -> None:
21
+ self.console.print(message, end="", style=style, soft_wrap=True)
22
+
23
+ def writeln(self, message: str = "", style: str = "") -> None:
24
+ self.console.print(message, style=style, soft_wrap=True)
@@ -0,0 +1 @@
1
+ """System subprocess wrappers for Bake."""
@@ -0,0 +1,21 @@
1
+ import subprocess
2
+
3
+ from bake.exceptions import ErrorCopyingException
4
+
5
+
6
+ class CopySubprocess:
7
+ """Copy files and directories via subprocess."""
8
+
9
+ def copy(self, source, destination, options=None):
10
+ if options is None:
11
+ options = []
12
+
13
+ command = ["cp"] + options + [source, destination]
14
+ result = subprocess.run(command, capture_output=True, text=True)
15
+
16
+ if result.returncode != 0:
17
+ raise ErrorCopyingException(
18
+ "Error copying default configuration files: " + str(result.stderr)
19
+ )
20
+
21
+ return result
@@ -0,0 +1,29 @@
1
+ import subprocess
2
+
3
+ from bake.exceptions import ErrorCloningRepositoryException, MissingPackageException
4
+
5
+
6
+ class GitSubprocess:
7
+ def clone(self, repo_url, target_path):
8
+ """Clone a git repository to the target path."""
9
+ try:
10
+ version_check = subprocess.run(
11
+ ["git", "--version"], capture_output=True, text=True
12
+ )
13
+ if version_check.returncode != 0:
14
+ raise MissingPackageException("git is not installed or not available in PATH")
15
+
16
+ result = subprocess.run(
17
+ ["git", "clone", repo_url, str(target_path)],
18
+ capture_output=True,
19
+ text=True,
20
+ )
21
+ if result.returncode != 0:
22
+ raise ErrorCloningRepositoryException(
23
+ f"Failed to clone {repo_url}: {result.stderr}"
24
+ )
25
+ return result.stdout
26
+ except Exception as exc:
27
+ raise ErrorCloningRepositoryException(
28
+ f"Failed to clone {repo_url}: {exc}"
29
+ ) from exc
bake/utils/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Utility modules for Bake."""
@@ -0,0 +1,82 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from bake.exceptions import (
5
+ BakePathNotConfiguredException,
6
+ ErrorCreatingConfigFolderException,
7
+ ErrorCreatingRepoFolderException,
8
+ NoConfigurationsFolderException,
9
+ )
10
+
11
+
12
+ class BakeSetup:
13
+ def bake_path(self):
14
+ bake_path = self.__get_env_path()
15
+ if not bake_path:
16
+ raise BakePathNotConfiguredException()
17
+ return bake_path
18
+
19
+ def config_path(self):
20
+ return Path(self.bake_path()) / "config"
21
+
22
+ def repositories_path(self):
23
+ return Path(self.bake_path()) / "repositories"
24
+
25
+ def bin_path(self):
26
+ return Path(self.bake_path()) / "bin"
27
+
28
+ def config_dir_exists(self):
29
+ return self.config_path().exists()
30
+
31
+ def repositories_dir_exists(self):
32
+ return self.repositories_path().exists()
33
+
34
+ def bin_dir_exists(self):
35
+ return self.bin_path().exists()
36
+
37
+ def is_config_dir_empty(self):
38
+ if not self.config_dir_exists():
39
+ raise NoConfigurationsFolderException(
40
+ "No bake configuration path found at "
41
+ + str(self.config_path().absolute())
42
+ )
43
+ return not os.listdir(self.config_path())
44
+
45
+ def setup_config_dir(self):
46
+ try:
47
+ if not self.config_dir_exists():
48
+ self.config_path().mkdir(parents=True, exist_ok=True)
49
+ except Exception as exc:
50
+ raise ErrorCreatingConfigFolderException(
51
+ "Error creating configuration folder "
52
+ + str(self.config_path().absolute())
53
+ + ": "
54
+ + str(exc)
55
+ ) from exc
56
+
57
+ def setup_repositories_dir(self):
58
+ try:
59
+ if not self.repositories_dir_exists():
60
+ self.repositories_path().mkdir(parents=True, exist_ok=True)
61
+ except Exception as exc:
62
+ raise ErrorCreatingRepoFolderException(
63
+ "Error creating repositories folder "
64
+ + str(self.repositories_path().absolute())
65
+ + ": "
66
+ + str(exc)
67
+ ) from exc
68
+
69
+ def setup_bin_dir(self):
70
+ try:
71
+ if not self.bin_dir_exists():
72
+ self.bin_path().mkdir(parents=True, exist_ok=True)
73
+ except Exception as exc:
74
+ raise ErrorCreatingRepoFolderException(
75
+ "Error creating bin folder "
76
+ + str(self.bin_path().absolute())
77
+ + ": "
78
+ + str(exc)
79
+ ) from exc
80
+
81
+ def __get_env_path(self):
82
+ return os.getenv("BAKE_PATH", "")
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: bake
3
+ Version: 0.1.0
4
+ Summary: Bake orchestrates internal CLI tools, setup scripts, and repeatable automation workflows.
5
+ Keywords: automation,cli,developer-tools,scripts,workflow
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Classifier: Topic :: Utilities
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: python-dotenv>=1.1.1
18
+ Requires-Dist: rich>=14.1.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Bake
22
+
23
+ Bake is a CLI automation orchestrator for managing internal tool repositories,
24
+ setup scripts, and reusable command workflows.
25
+
26
+ ## Why Bake
27
+
28
+ - Organize automation in one place instead of scattered shell scripts.
29
+ - Bootstrap and install tool repositories from JSON definitions.
30
+ - Expose runnable binaries in a shared `bin` directory.
31
+ - Keep local setup repeatable across developers.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ uv venv
37
+ source .venv/bin/activate
38
+ uv pip install -e .
39
+ ```
40
+
41
+ After installation, the main command is:
42
+
43
+ ```bash
44
+ bake
45
+ ```
46
+
47
+ ## Commands
48
+
49
+ ### `bake install`
50
+
51
+ Interactive initialization flow that:
52
+
53
+ - Configures `BAKE_PATH` in `.env`.
54
+ - Creates `config`, `repositories`, and `bin` folders.
55
+ - Copies default configuration templates from `tools/config`.
56
+ - Optionally downloads and installs configured repositories.
57
+
58
+ ### `bake dpkg sync-config`
59
+
60
+ Synchronizes default configuration definitions from `tools/config` into
61
+ `$BAKE_PATH/config`.
62
+
63
+ ## Configuration Model
64
+
65
+ Bake uses JSON files in `tools/config`, for example:
66
+
67
+ ```json
68
+ {
69
+ "name": "aws-cli-toolkit",
70
+ "repo_url": "https://github.com/darioriverat/aws-cli-toolkit",
71
+ "description": "A comprehensive command-line toolkit for managing AWS services",
72
+ "scripts": {
73
+ "install": [
74
+ "uv venv",
75
+ "source .venv/bin/activate",
76
+ "uv pip install -e ."
77
+ ]
78
+ }
79
+ }
80
+ ```
81
+
82
+ Optional `binaries` entries can create symlinks/wrappers in `$BAKE_PATH/bin`.
83
+
84
+ ## Notes
85
+
86
+ - `BAKE_PATH` is the environment variable used by Bake.
87
+ - `bake` is the canonical command.
@@ -0,0 +1,23 @@
1
+ bake/__init__.py,sha256=C2nrEhouHcXSnMN-0auN78UG3TiKuHO5gyWQ7KQ2HHU,20
2
+ bake/cli.py,sha256=KPf2SC1NQRvqzWOHftS6B8h2b1XG_b87TyxGbqt_lD8,882
3
+ bake/dpkg.py,sha256=1--Jb-SOOds1OTOx3A-TebA3P1Yr7B4N34MlfEjh334,849
4
+ bake/exceptions.py,sha256=XC3fnWPO0RC2gj2p2uvc1Goqdg-ABfMO_lcninffRHE,873
5
+ bake/install.py,sha256=eYjPlqi8-AYKq0qPUTMKcqYeDPf68B6IIg5kgSC3PIM,211
6
+ bake/output_style.py,sha256=ngXYLgiBIW0bd1_2bjALt5c0xVNI2ddt4IfxamcpJHs,693
7
+ bake/commands/__init__.py,sha256=i3puFYleYrC9wrXzAgXeRtsHaooBuMR25FKps4_k08c,28
8
+ bake/commands/dpkg/__init__.py,sha256=K-J8KlEr6xHMMX46PoPT9xrqIUnl5hQG3fnuzVanqe0,51
9
+ bake/commands/dpkg/bake_dpkg_command.py,sha256=QefP45ZemarzVS9dGbHwfameeth1NWeVc9GF-7KZhWo,1611
10
+ bake/commands/init/__init__.py,sha256=5zqy0RkAbIKvllKF3zM7cAMS3izJxjz08ZxNcnbMTks,47
11
+ bake/commands/init/bake_downloader.py,sha256=Nuqv4AjzTOYLwPjNeVU3DoKNli_Y2XJR90174pPkVHQ,1740
12
+ bake/commands/init/bake_init_command.py,sha256=l0X96TtgLJexbKBwk5nwEzAhyWrgnRBYNPJxbhD5_2M,8368
13
+ bake/commands/init/bake_installer.py,sha256=hk1DLR3rZEO2Mw2n4nMy63TxHazCIKbOjyF8Mfq2BKQ,8609
14
+ bake/commands/init/bake_path.py,sha256=MdwJ5Fyl3qo8kh1TnhJqYliaYVGhF5-ajgGEhuibFyA,1760
15
+ bake/system/__init__.py,sha256=E3Uyp47qDRFGTmUa6rV-87k2vaV5j0Fd3RqiVOm0ul0,43
16
+ bake/system/copy_subprocess.py,sha256=QxJX92XC3bzMJSmjBtwF0fdiPJTzE9Luk5Lhv4gIb6s,585
17
+ bake/system/git_subprocess.py,sha256=GBBF_-Qyle4-Ae2ERvdDU5KhWN8ARmkfQ1pMB_3BOho,1059
18
+ bake/utils/__init__.py,sha256=VhOkMFl6hqC5ezszTwSdb1hKjktf6Npjezn7-aDEe8c,32
19
+ bake/utils/bake_setup.py,sha256=96Xi77YMx_93DjbzTt_F4Kat0-MBzbsfG19YB2y0fxY,2538
20
+ bake-0.1.0.dist-info/METADATA,sha256=JfqyQ8veefEUQJkl3wcx6ojpf6obrIbVX0G751J0pak,2306
21
+ bake-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
22
+ bake-0.1.0.dist-info/entry_points.txt,sha256=L7IwlY_HKD-yw5GyQyBYeYdN5oium1yWGmsNiMktjRI,39
23
+ bake-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bake = bake.cli:main