deploynator 1.1.1__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.
- deploynator/__init__.py +4 -0
- deploynator/__main__.py +282 -0
- deploynator/_app.py +572 -0
- deploynator/_changelog.py +116 -0
- deploynator/_config.py +100 -0
- deploynator/_deploy.py +545 -0
- deploynator/_docs.py +49 -0
- deploynator/_git.py +244 -0
- deploynator/_mcp_server.py +278 -0
- deploynator/_models.py +282 -0
- deploynator/_platforms.py +251 -0
- deploynator/_pypi.py +155 -0
- deploynator/_runner.py +51 -0
- deploynator/_semver.py +135 -0
- deploynator/_toml.py +100 -0
- deploynator-1.1.1.dist-info/METADATA +294 -0
- deploynator-1.1.1.dist-info/RECORD +20 -0
- deploynator-1.1.1.dist-info/WHEEL +4 -0
- deploynator-1.1.1.dist-info/entry_points.txt +3 -0
- deploynator-1.1.1.dist-info/licenses/LICENSE +661 -0
deploynator/__init__.py
ADDED
deploynator/__main__.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from typing import NoReturn
|
|
6
|
+
|
|
7
|
+
from deploynator import DeploynatorApp
|
|
8
|
+
from deploynator._config import Config
|
|
9
|
+
from deploynator._models import CommitType
|
|
10
|
+
|
|
11
|
+
KNOWN_COMMANDS = {
|
|
12
|
+
"release", "commit", "start", "auth", "docs", "init",
|
|
13
|
+
"status", "projects", "workflow", "install", "interactive", "serve",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
prog="deploynator",
|
|
20
|
+
description="Multi-platform deployer for GitHub, GitLab, and Bitbucket.",
|
|
21
|
+
epilog="Deployed with deploynator (https://github.com/grisuno/deploynator)",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--version", action="version",
|
|
25
|
+
version=f"deploynator {__import__('deploynator').__version__}",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--workdir", type=str, default="",
|
|
29
|
+
help="Working directory (default: current dir)",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
33
|
+
|
|
34
|
+
# release
|
|
35
|
+
release_p = subparsers.add_parser("release", help="Create a release")
|
|
36
|
+
release_p.add_argument(
|
|
37
|
+
"--type", dest="commit_type", default="release",
|
|
38
|
+
choices=CommitType.valid_types(),
|
|
39
|
+
help="Commit type (default: release)",
|
|
40
|
+
)
|
|
41
|
+
release_p.add_argument("--subject", type=str, default="", help="Commit subject")
|
|
42
|
+
release_p.add_argument("--scope", type=str, default="", help="Commit scope")
|
|
43
|
+
release_p.add_argument("--body", type=str, default="", help="Commit body")
|
|
44
|
+
release_p.add_argument("--project", "-p", type=str, default="", help="Project name from projects/")
|
|
45
|
+
release_p.add_argument("--bump", type=str, default="", choices=["major", "minor", "patch", ""],
|
|
46
|
+
help="Force version bump")
|
|
47
|
+
release_p.add_argument("--dry-run", action="store_true", help="Simulate without changes")
|
|
48
|
+
release_p.add_argument("--no-test", dest="no_test", action="store_true",
|
|
49
|
+
help="Skip tests (like DEPLOY.sh --no-test)")
|
|
50
|
+
release_p.add_argument("--no-docs", dest="no_docs", action="store_true",
|
|
51
|
+
help="Skip readmenator docs regeneration")
|
|
52
|
+
release_p.add_argument("--no-pypi", dest="no_pypi", action="store_true",
|
|
53
|
+
help="Skip PyPI build+upload")
|
|
54
|
+
release_p.add_argument("--platforms", type=str, nargs="*",
|
|
55
|
+
choices=["github", "gitlab", "bitbucket"],
|
|
56
|
+
help="Platforms to deploy to")
|
|
57
|
+
release_p.add_argument("--all", dest="all_projects", action="store_true",
|
|
58
|
+
help="Deploy all discovered projects")
|
|
59
|
+
|
|
60
|
+
# commit
|
|
61
|
+
commit_p = subparsers.add_parser("commit", help="Create a conventional commit")
|
|
62
|
+
commit_p.add_argument(
|
|
63
|
+
"--type", dest="commit_type", required=True,
|
|
64
|
+
choices=CommitType.valid_types(),
|
|
65
|
+
help="Commit type",
|
|
66
|
+
)
|
|
67
|
+
commit_p.add_argument("--subject", type=str, required=True, help="Commit subject")
|
|
68
|
+
commit_p.add_argument("--scope", type=str, default="", help="Commit scope")
|
|
69
|
+
commit_p.add_argument("--body", type=str, default="", help="Commit body")
|
|
70
|
+
commit_p.add_argument("--project", "-p", type=str, default="", help="Project name from projects/")
|
|
71
|
+
commit_p.add_argument("--dry-run", action="store_true", help="Simulate without changes")
|
|
72
|
+
|
|
73
|
+
# start
|
|
74
|
+
start_p = subparsers.add_parser("start", help="Create a feature/bug branch")
|
|
75
|
+
start_p.add_argument(
|
|
76
|
+
"--type", dest="commit_type", required=True,
|
|
77
|
+
choices=CommitType.valid_types(),
|
|
78
|
+
help="Branch type",
|
|
79
|
+
)
|
|
80
|
+
start_p.add_argument("--desc", type=str, required=True, help="Branch description")
|
|
81
|
+
start_p.add_argument("--project", "-p", type=str, default="", help="Project name from projects/")
|
|
82
|
+
start_p.add_argument("--remote", action="store_true", help="Push branch to remote")
|
|
83
|
+
|
|
84
|
+
# auth
|
|
85
|
+
auth_p = subparsers.add_parser("auth", help="Authentication commands")
|
|
86
|
+
auth_sub = auth_p.add_subparsers(dest="auth_command", help="Auth subcommands")
|
|
87
|
+
auth_login_p = auth_sub.add_parser("login", help="Login to platforms")
|
|
88
|
+
auth_login_p.add_argument(
|
|
89
|
+
"--platforms", type=str, nargs="*",
|
|
90
|
+
choices=["github", "gitlab", "bitbucket"],
|
|
91
|
+
help="Platforms to authenticate (default: all)",
|
|
92
|
+
)
|
|
93
|
+
auth_sub.add_parser("status", help="Show authentication status")
|
|
94
|
+
|
|
95
|
+
# docs
|
|
96
|
+
docs_p = subparsers.add_parser("docs", help="Generate documentation via ReadMenator")
|
|
97
|
+
docs_p.add_argument("--project", "-p", type=str, default="", help="Project name from projects/")
|
|
98
|
+
|
|
99
|
+
# init
|
|
100
|
+
init_p = subparsers.add_parser("init", help="Initialize deploynator in project")
|
|
101
|
+
init_p.add_argument("--name", type=str, default="", help="Project name")
|
|
102
|
+
|
|
103
|
+
# status
|
|
104
|
+
status_p = subparsers.add_parser("status", help="Show project status")
|
|
105
|
+
status_p.add_argument("--project", "-p", type=str, default="", help="Project name from projects/")
|
|
106
|
+
|
|
107
|
+
# projects
|
|
108
|
+
subparsers.add_parser("projects", help="List discovered projects")
|
|
109
|
+
|
|
110
|
+
# workflow
|
|
111
|
+
workflow_p = subparsers.add_parser("workflow", help="Generate CI/CD workflows")
|
|
112
|
+
workflow_p.add_argument("--project", "-p", type=str, default="", help="Specific project")
|
|
113
|
+
workflow_p.add_argument("--all", action="store_true", help="All projects")
|
|
114
|
+
|
|
115
|
+
# install
|
|
116
|
+
install_p = subparsers.add_parser("install", help="Install platform dependencies")
|
|
117
|
+
install_p.add_argument("--project", "-p", type=str, default="", help="Specific project")
|
|
118
|
+
|
|
119
|
+
# interactive (no args/flags — bare mode menu)
|
|
120
|
+
subparsers.add_parser("interactive", help="Start interactive menu (also default with no args)")
|
|
121
|
+
|
|
122
|
+
# serve (MCP stdio server for agent harnesses)
|
|
123
|
+
serve_p = subparsers.add_parser("serve", help="Start MCP stdio server")
|
|
124
|
+
serve_p.add_argument("path", type=str, nargs="?", default=".",
|
|
125
|
+
help="Target directory (default: .)")
|
|
126
|
+
|
|
127
|
+
return parser
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def preprocess_argv(argv: list[str]) -> tuple[argparse.Namespace, str]:
|
|
131
|
+
parser = build_parser()
|
|
132
|
+
extracted_project = ""
|
|
133
|
+
|
|
134
|
+
if not argv or argv[0] in ("--help", "-h", "--version"):
|
|
135
|
+
return parser.parse_args(argv), extracted_project
|
|
136
|
+
|
|
137
|
+
# Scan past global flags (--workdir, etc.) to find first positional token
|
|
138
|
+
first_pos = None
|
|
139
|
+
first_pos_idx = None
|
|
140
|
+
i = 0
|
|
141
|
+
while i < len(argv):
|
|
142
|
+
arg = argv[i]
|
|
143
|
+
if not arg.startswith("-"):
|
|
144
|
+
first_pos = arg
|
|
145
|
+
first_pos_idx = i
|
|
146
|
+
break
|
|
147
|
+
if arg in ("--workdir",):
|
|
148
|
+
i += 2
|
|
149
|
+
else:
|
|
150
|
+
i += 1
|
|
151
|
+
|
|
152
|
+
if first_pos is not None and first_pos not in KNOWN_COMMANDS:
|
|
153
|
+
extracted_project = first_pos
|
|
154
|
+
argv = argv[:first_pos_idx] + argv[first_pos_idx + 1:]
|
|
155
|
+
|
|
156
|
+
args = parser.parse_args(argv)
|
|
157
|
+
|
|
158
|
+
if hasattr(args, "project") and not args.project and extracted_project:
|
|
159
|
+
args.project = extracted_project
|
|
160
|
+
|
|
161
|
+
return args, extracted_project
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main(argv: list[str] | None = None) -> NoReturn | None:
|
|
165
|
+
raw_argv = list(argv) if argv is not None else sys.argv[1:]
|
|
166
|
+
|
|
167
|
+
# Bare `deploynator` (no args) = interactive menu. Flags = headless.
|
|
168
|
+
if not raw_argv:
|
|
169
|
+
config = Config.create()
|
|
170
|
+
DeploynatorApp(config).interactive_menu()
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
args, project_pos = preprocess_argv(raw_argv)
|
|
174
|
+
|
|
175
|
+
config = Config.create()
|
|
176
|
+
app = DeploynatorApp(config, workdir=args.workdir if hasattr(args, "workdir") else "")
|
|
177
|
+
|
|
178
|
+
if not args.command:
|
|
179
|
+
parser = build_parser()
|
|
180
|
+
parser.print_help()
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
project = getattr(args, "project", "") or project_pos
|
|
184
|
+
|
|
185
|
+
if args.command == "release":
|
|
186
|
+
results = app.release(
|
|
187
|
+
commit_type=args.commit_type,
|
|
188
|
+
subject=args.subject,
|
|
189
|
+
scope=args.scope,
|
|
190
|
+
body=args.body,
|
|
191
|
+
project_name=project,
|
|
192
|
+
bump=args.bump if hasattr(args, "bump") else "",
|
|
193
|
+
dry_run=args.dry_run,
|
|
194
|
+
platforms=args.platforms if hasattr(args, "platforms") else None,
|
|
195
|
+
all_projects=args.all_projects if hasattr(args, "all_projects") else False,
|
|
196
|
+
no_test=getattr(args, "no_test", False),
|
|
197
|
+
no_docs=getattr(args, "no_docs", False),
|
|
198
|
+
no_pypi=getattr(args, "no_pypi", False),
|
|
199
|
+
)
|
|
200
|
+
sys.exit(0 if all(r.success for r in results) else 1)
|
|
201
|
+
|
|
202
|
+
elif args.command == "commit":
|
|
203
|
+
result = app.commit(
|
|
204
|
+
commit_type=args.commit_type,
|
|
205
|
+
subject=args.subject,
|
|
206
|
+
scope=args.scope,
|
|
207
|
+
body=args.body,
|
|
208
|
+
project_name=project,
|
|
209
|
+
dry_run=args.dry_run,
|
|
210
|
+
)
|
|
211
|
+
print(result)
|
|
212
|
+
|
|
213
|
+
elif args.command == "start":
|
|
214
|
+
result = app.start_branch(
|
|
215
|
+
commit_type=args.commit_type,
|
|
216
|
+
description=args.desc,
|
|
217
|
+
project_name=project,
|
|
218
|
+
remote=args.remote,
|
|
219
|
+
)
|
|
220
|
+
print(result)
|
|
221
|
+
|
|
222
|
+
elif args.command == "auth":
|
|
223
|
+
if args.auth_command == "login":
|
|
224
|
+
platforms = getattr(args, "platforms", None)
|
|
225
|
+
results = app.auth_login(platforms)
|
|
226
|
+
for p, ok in results.items():
|
|
227
|
+
print(f" {p}: {'OK' if ok else 'FAIL'}")
|
|
228
|
+
elif args.auth_command == "status":
|
|
229
|
+
status = app.auth_status()
|
|
230
|
+
for p, ok in status.items():
|
|
231
|
+
print(f" {p}: {'authenticated' if ok else 'not authenticated'}")
|
|
232
|
+
else:
|
|
233
|
+
build_parser().parse_args(["auth", "--help"])
|
|
234
|
+
|
|
235
|
+
elif args.command == "docs":
|
|
236
|
+
ok = app.generate_docs(project_name=project)
|
|
237
|
+
print("[+] Documentation generated" if ok else "[!] Documentation failed")
|
|
238
|
+
|
|
239
|
+
elif args.command == "init":
|
|
240
|
+
result = app.init_project(name=args.name)
|
|
241
|
+
print(result)
|
|
242
|
+
|
|
243
|
+
elif args.command == "status":
|
|
244
|
+
info = app.status(project_name=project)
|
|
245
|
+
for k, v in info.items():
|
|
246
|
+
print(f" {k}: {v}")
|
|
247
|
+
|
|
248
|
+
elif args.command == "projects":
|
|
249
|
+
projects = app.list_projects()
|
|
250
|
+
if not projects:
|
|
251
|
+
print("No projects found.")
|
|
252
|
+
return
|
|
253
|
+
for p in projects:
|
|
254
|
+
platforms = ", ".join(
|
|
255
|
+
f"{pc.platform.label()}/{pc.repo}" for pc in p.platforms if pc.enabled
|
|
256
|
+
)
|
|
257
|
+
print(f" {p.name}: {platforms}" if platforms else f" {p.name}")
|
|
258
|
+
|
|
259
|
+
elif args.command == "workflow":
|
|
260
|
+
created = app.generate_ci(
|
|
261
|
+
project_name=project,
|
|
262
|
+
all_projects=getattr(args, "all", False),
|
|
263
|
+
)
|
|
264
|
+
for path in created:
|
|
265
|
+
print(f"[+] Created {path}")
|
|
266
|
+
|
|
267
|
+
elif args.command == "install":
|
|
268
|
+
deps = app.install_deps(project_name=project)
|
|
269
|
+
for p, ok in deps.items():
|
|
270
|
+
print(f" {p}: {'available' if ok else 'not found'}")
|
|
271
|
+
|
|
272
|
+
elif args.command == "interactive":
|
|
273
|
+
app.interactive_menu()
|
|
274
|
+
|
|
275
|
+
elif args.command == "serve":
|
|
276
|
+
from deploynator._mcp_server import MCPServer
|
|
277
|
+
target = getattr(args, "path", ".") or "."
|
|
278
|
+
MCPServer(app, target).run()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == "__main__":
|
|
282
|
+
main()
|