clue2app-cli 0.2.7__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.
- clue2app_cli/__init__.py +10 -0
- clue2app_cli/agents/__init__.py +9 -0
- clue2app_cli/agents/ecr_healer.py +475 -0
- clue2app_cli/auth.py +343 -0
- clue2app_cli/config.py +316 -0
- clue2app_cli/core_client.py +46 -0
- clue2app_cli/creds_module.py +253 -0
- clue2app_cli/curses_wizard.py +839 -0
- clue2app_cli/entrypoint_check.py +215 -0
- clue2app_cli/main.py +6711 -0
- clue2app_cli/sdk.py +2993 -0
- clue2app_cli/tui/__init__.py +33 -0
- clue2app_cli/tui/curses_app.py +733 -0
- clue2app_cli/tui/data.py +316 -0
- clue2app_cli/tui/screens/__init__.py +1 -0
- clue2app_cli/tui/textual_app.py +1021 -0
- clue2app_cli/tui/widgets/__init__.py +1 -0
- clue2app_cli/tui_wizard.py +987 -0
- clue2app_cli/version.py +3 -0
- clue2app_cli/wizard.py +1079 -0
- clue2app_cli-0.2.7.dist-info/METADATA +202 -0
- clue2app_cli-0.2.7.dist-info/RECORD +26 -0
- clue2app_cli-0.2.7.dist-info/WHEEL +5 -0
- clue2app_cli-0.2.7.dist-info/entry_points.txt +3 -0
- clue2app_cli-0.2.7.dist-info/licenses/LICENSE +21 -0
- clue2app_cli-0.2.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Pre-flight check that catches common "app will build but never start"
|
|
2
|
+
mistakes before c2a creates an app on the platform.
|
|
3
|
+
|
|
4
|
+
Background: Paketo buildpacks happily produce a runnable image even when
|
|
5
|
+
the source has no declared web entry point. The container then exits with
|
|
6
|
+
ExitCode 0 at launch because there's nothing to run, and the Knative
|
|
7
|
+
service sits in a permanent failed state. Most users won't recognize
|
|
8
|
+
this — they'll see "build succeeded" and assume the deploy worked.
|
|
9
|
+
|
|
10
|
+
This module shallow-clones the user's repo, inspects the file layout,
|
|
11
|
+
and returns a human-readable error explaining what's missing if it
|
|
12
|
+
spots one of the known traps. The SDK then refuses to create the app
|
|
13
|
+
and the user fixes their repo before re-running.
|
|
14
|
+
|
|
15
|
+
Detection is intentionally conservative — only reject when we're highly
|
|
16
|
+
confident something is wrong. Unknown frameworks pass through.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import tempfile
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Paketo Node-Start auto-detects these (and .cjs/.mjs variants) at repo root.
|
|
29
|
+
_NODE_AUTO_ENTRYPOINTS = {
|
|
30
|
+
"server.js", "server.cjs", "server.mjs",
|
|
31
|
+
"app.js", "app.cjs", "app.mjs",
|
|
32
|
+
"main.js", "main.cjs", "main.mjs",
|
|
33
|
+
"index.js", "index.cjs", "index.mjs",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Python frameworks Paketo CAN auto-start without a Procfile.
|
|
37
|
+
# Django: manage.py at root + wsgi.py somewhere.
|
|
38
|
+
# Generic WSGI/ASGI apps and FastAPI/Flask all need an explicit Procfile.
|
|
39
|
+
_PYTHON_DJANGO_MARKERS = {"manage.py"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class EntryPointIssue:
|
|
44
|
+
"""A detected problem with the repo's web entry point.
|
|
45
|
+
|
|
46
|
+
`summary` is one-liner safe for log lines / table cells.
|
|
47
|
+
`detail` is multi-line guidance suitable for showing to the user.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
summary: str
|
|
51
|
+
detail: str
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return f"{self.summary}\n\n{self.detail}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def detect_entrypoint_issue(
|
|
58
|
+
git_url: str,
|
|
59
|
+
*,
|
|
60
|
+
branch: str = "main",
|
|
61
|
+
git_username: str = "",
|
|
62
|
+
git_password: str = "",
|
|
63
|
+
) -> EntryPointIssue | None:
|
|
64
|
+
"""Shallow-clone `git_url` at `branch` and inspect for known traps.
|
|
65
|
+
|
|
66
|
+
Returns None if the repo looks runnable, or an `EntryPointIssue`
|
|
67
|
+
describing the specific gap. Network/clone failures return None so
|
|
68
|
+
we don't block users on transient issues — Paketo will surface the
|
|
69
|
+
real problem at build time as a fallback.
|
|
70
|
+
"""
|
|
71
|
+
auth_url = _inject_basic_auth(git_url, git_username, git_password)
|
|
72
|
+
|
|
73
|
+
tmp = Path(tempfile.mkdtemp(prefix="c2a-entry-check-"))
|
|
74
|
+
try:
|
|
75
|
+
proc = subprocess.run(
|
|
76
|
+
["git", "clone", "--depth", "1", "--branch", branch, "--quiet", auth_url, str(tmp)],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
timeout=30,
|
|
80
|
+
)
|
|
81
|
+
if proc.returncode != 0:
|
|
82
|
+
# Don't block on clone failures — wrong branch name, transient
|
|
83
|
+
# network, private repo with bad creds, etc. The platform's own
|
|
84
|
+
# build step will surface these clearly enough.
|
|
85
|
+
return None
|
|
86
|
+
return _inspect_repo(tmp)
|
|
87
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
88
|
+
return None
|
|
89
|
+
finally:
|
|
90
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _inject_basic_auth(git_url: str, username: str, password: str) -> str:
|
|
94
|
+
"""Embed credentials into an https URL so the clone doesn't prompt."""
|
|
95
|
+
if not password or not git_url.startswith("https://"):
|
|
96
|
+
return git_url
|
|
97
|
+
user = username or "x-oauth-basic"
|
|
98
|
+
return git_url.replace("https://", f"https://{user}:{password}@", 1)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _inspect_repo(root: Path) -> EntryPointIssue | None:
|
|
102
|
+
"""Decide whether the repo declares a working web entry point."""
|
|
103
|
+
has_procfile_web = _procfile_has_web_line(root / "Procfile")
|
|
104
|
+
files_at_root = {p.name for p in root.iterdir() if p.is_file()}
|
|
105
|
+
|
|
106
|
+
# Java: any pom.xml / build.gradle / build.gradle.kts → buildpack runs
|
|
107
|
+
# the produced JAR. Always fine.
|
|
108
|
+
if {"pom.xml", "build.gradle", "build.gradle.kts"} & files_at_root:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Go: go.mod → buildpack runs the compiled binary. Always fine.
|
|
112
|
+
if "go.mod" in files_at_root:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
# Node: needs Procfile OR one of the auto-detected entrypoints at root.
|
|
116
|
+
if "package.json" in files_at_root:
|
|
117
|
+
if has_procfile_web:
|
|
118
|
+
return None
|
|
119
|
+
if files_at_root & _NODE_AUTO_ENTRYPOINTS:
|
|
120
|
+
return None
|
|
121
|
+
return EntryPointIssue(
|
|
122
|
+
summary="Node repo has no Procfile and no auto-detectable entry point",
|
|
123
|
+
detail=(
|
|
124
|
+
"Paketo's Node-Start buildpack only auto-launches one of:\n"
|
|
125
|
+
" server.js | app.js | main.js | index.js (or .cjs / .mjs variants)\n\n"
|
|
126
|
+
"Your repo has package.json but none of those at the root.\n"
|
|
127
|
+
"Add a Procfile, e.g. for an Express app:\n\n"
|
|
128
|
+
" web: node dist/server.js\n\n"
|
|
129
|
+
"Or rename your entry file to one of the auto-detected names."
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Python: FastAPI/Flask/generic WSGI need a Procfile. Django (manage.py)
|
|
134
|
+
# is auto-detected.
|
|
135
|
+
python_markers = {"requirements.txt", "pyproject.toml", "Pipfile"}
|
|
136
|
+
if files_at_root & python_markers:
|
|
137
|
+
if has_procfile_web:
|
|
138
|
+
return None
|
|
139
|
+
if files_at_root & _PYTHON_DJANGO_MARKERS:
|
|
140
|
+
return None
|
|
141
|
+
framework = _detect_python_framework(root, files_at_root)
|
|
142
|
+
return EntryPointIssue(
|
|
143
|
+
summary=f"{framework or 'Python'} repo has no Procfile (only Django auto-launches without one)",
|
|
144
|
+
detail=_python_procfile_guidance(framework),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Unknown stack — let it through. Paketo will surface anything wrong.
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _procfile_has_web_line(procfile_path: Path) -> bool:
|
|
152
|
+
if not procfile_path.is_file():
|
|
153
|
+
return False
|
|
154
|
+
try:
|
|
155
|
+
for raw_line in procfile_path.read_text(errors="replace").splitlines():
|
|
156
|
+
line = raw_line.strip()
|
|
157
|
+
if not line or line.startswith("#"):
|
|
158
|
+
continue
|
|
159
|
+
# Procfile format: `<process-type>: <command>`
|
|
160
|
+
if re.match(r"^web\s*:\s*\S", line):
|
|
161
|
+
return True
|
|
162
|
+
except OSError:
|
|
163
|
+
return False
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _detect_python_framework(root: Path, files_at_root: set[str]) -> str | None:
|
|
168
|
+
"""Best-effort framework detection from common requirements files."""
|
|
169
|
+
needles = (
|
|
170
|
+
("FastAPI", "fastapi"),
|
|
171
|
+
("Flask", "flask"),
|
|
172
|
+
)
|
|
173
|
+
for candidate in ("requirements.txt", "pyproject.toml", "Pipfile"):
|
|
174
|
+
path = root / candidate
|
|
175
|
+
if path.name not in files_at_root:
|
|
176
|
+
continue
|
|
177
|
+
try:
|
|
178
|
+
content = path.read_text(errors="replace").lower()
|
|
179
|
+
except OSError:
|
|
180
|
+
continue
|
|
181
|
+
for label, needle in needles:
|
|
182
|
+
if needle in content:
|
|
183
|
+
return label
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _python_procfile_guidance(framework: str | None) -> str:
|
|
188
|
+
if framework == "FastAPI":
|
|
189
|
+
return (
|
|
190
|
+
"Paketo's Python buildpack only auto-launches Django apps.\n"
|
|
191
|
+
"Detected FastAPI in your requirements; add a Procfile at the\n"
|
|
192
|
+
"repo root with the start command, e.g.:\n\n"
|
|
193
|
+
" web: uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}\n\n"
|
|
194
|
+
"Replace `main:app` with `<module>:<app-variable>` if your\n"
|
|
195
|
+
"FastAPI app lives in a different file."
|
|
196
|
+
)
|
|
197
|
+
if framework == "Flask":
|
|
198
|
+
return (
|
|
199
|
+
"Paketo's Python buildpack only auto-launches Django apps.\n"
|
|
200
|
+
"Detected Flask in your requirements; add a Procfile at the\n"
|
|
201
|
+
"repo root with the start command, e.g.:\n\n"
|
|
202
|
+
" web: gunicorn app:app --bind 0.0.0.0:${PORT:-8080}\n\n"
|
|
203
|
+
"Replace `app:app` with `<module>:<app-variable>` if your\n"
|
|
204
|
+
"Flask app lives in a different file."
|
|
205
|
+
)
|
|
206
|
+
return (
|
|
207
|
+
"Paketo's Python buildpack only auto-launches Django apps\n"
|
|
208
|
+
"(detected via manage.py + wsgi.py). Your repo doesn't have that\n"
|
|
209
|
+
"layout, so add a Procfile at the repo root with the start command.\n\n"
|
|
210
|
+
"Examples:\n"
|
|
211
|
+
" web: uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080} # FastAPI\n"
|
|
212
|
+
" web: gunicorn app:app --bind 0.0.0.0:${PORT:-8080} # Flask / WSGI\n"
|
|
213
|
+
" web: python my_server.py # generic\n\n"
|
|
214
|
+
"Then commit + push and re-run the create command."
|
|
215
|
+
)
|