deployforge 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {deployforge-0.1.0/src/deployforge.egg-info → deployforge-0.1.2}/PKG-INFO +1 -1
- {deployforge-0.1.0 → deployforge-0.1.2}/pyproject.toml +1 -1
- deployforge-0.1.2/src/deployforge/__init__.py +5 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/__version__.py +1 -1
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/project.py +5 -1
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/providers/vercel.py +77 -20
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/ui/terminal.py +39 -26
- {deployforge-0.1.0 → deployforge-0.1.2/src/deployforge.egg-info}/PKG-INFO +1 -1
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_cli.py +3 -1
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_providers.py +24 -8
- deployforge-0.1.0/src/deployforge/__init__.py +0 -3
- {deployforge-0.1.0 → deployforge-0.1.2}/LICENSE +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/README.md +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/setup.cfg +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/backend.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/database.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/frontend.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/analyzer/shared.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/cli.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/config.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/deployment/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/deployment/orchestrator.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/deployment/planner.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/deployment/verifier.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/errors/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/github/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/github/integration.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/integration/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/integration/cors.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/integration/environment.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/integration/frontend_backend.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/providers/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/providers/base.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/providers/render.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/security/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/security/gitignore.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/security/scanner.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/security/secrets.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge/ui/__init__.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge.egg-info/SOURCES.txt +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge.egg-info/dependency_links.txt +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge.egg-info/entry_points.txt +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge.egg-info/requires.txt +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/src/deployforge.egg-info/top_level.txt +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_analyzer.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_config.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_integration.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_planner.py +0 -0
- {deployforge-0.1.0 → deployforge-0.1.2}/tests/test_security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deployforge
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: From GitHub to a Live Application. Automatically. DeployForge analyzes an application and deploys its frontend and backend to production.
|
|
5
5
|
Author-email: sifuna codex <www.antonysifuna07@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deployforge"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "From GitHub to a Live Application. Automatically. DeployForge analyzes an application and deploys its frontend and backend to production."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -376,9 +376,13 @@ def analyze_project(root: Path, config: ProjectConfig | None = None) -> ProjectA
|
|
|
376
376
|
if not has_git:
|
|
377
377
|
repo = RepoInfo(exists=False, branch=current_branch(root) or "main")
|
|
378
378
|
|
|
379
|
+
project_name = suggest_name(root)
|
|
380
|
+
if (repo.exists or has_git) and repo.name:
|
|
381
|
+
slug = re.sub(r"[^a-zA-Z0-9\-_.]+", "-", repo.name).strip("-._").lower()
|
|
382
|
+
project_name = slug or project_name
|
|
379
383
|
analysis = ProjectAnalysis(
|
|
380
384
|
root=root,
|
|
381
|
-
name=
|
|
385
|
+
name=project_name,
|
|
382
386
|
frontends=frontends,
|
|
383
387
|
backends=backends,
|
|
384
388
|
database=detect_database(root),
|
|
@@ -81,7 +81,6 @@ class VercelProvider(DeploymentProvider):
|
|
|
81
81
|
payload: dict[str, Any] = {
|
|
82
82
|
"name": name,
|
|
83
83
|
"gitRepository": {"type": "github", "repo": repo},
|
|
84
|
-
"public": not private,
|
|
85
84
|
}
|
|
86
85
|
preset = FRAMEWORK_PRESETS.get(framework or "", framework)
|
|
87
86
|
if preset:
|
|
@@ -89,25 +88,58 @@ class VercelProvider(DeploymentProvider):
|
|
|
89
88
|
if directory and directory not in (".", ""):
|
|
90
89
|
payload["rootDirectory"] = directory
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
try:
|
|
92
|
+
data = self.http.request(
|
|
93
|
+
"POST",
|
|
94
|
+
f"{API_BASE}/v10/projects",
|
|
95
|
+
headers=self._headers(),
|
|
96
|
+
json=payload,
|
|
97
|
+
)
|
|
98
|
+
except ProviderError as exc:
|
|
99
|
+
if exc.status_code == 409:
|
|
100
|
+
existing = self._fetch_project(name)
|
|
101
|
+
if existing and existing.get("id"):
|
|
102
|
+
return self._from_project(existing, name, framework)
|
|
103
|
+
raise
|
|
104
|
+
raise
|
|
98
105
|
if not isinstance(data, dict):
|
|
99
106
|
msg = "Vercel returned an unexpected response while creating the project."
|
|
100
107
|
raise ProviderError(msg)
|
|
101
|
-
|
|
102
|
-
project_name = str(data.get("name") or name)
|
|
103
|
-
if not project_id:
|
|
108
|
+
if not data.get("id"):
|
|
104
109
|
raise ProviderError("Vercel did not return a project id.")
|
|
110
|
+
return self._from_project(data, name, framework)
|
|
111
|
+
|
|
112
|
+
def _fetch_project(self, name: str) -> dict[str, Any] | None:
|
|
113
|
+
"""Read an existing project so a repeat run can reuse it."""
|
|
114
|
+
try:
|
|
115
|
+
data = self.http.request(
|
|
116
|
+
"GET",
|
|
117
|
+
f"{API_BASE}/v9/projects/{name}",
|
|
118
|
+
headers=self._headers(),
|
|
119
|
+
)
|
|
120
|
+
except ProviderError as exc:
|
|
121
|
+
if exc.status_code == 404:
|
|
122
|
+
return None
|
|
123
|
+
raise
|
|
124
|
+
return data if isinstance(data, dict) else None
|
|
125
|
+
|
|
126
|
+
def _from_project(
|
|
127
|
+
self, data: dict[str, Any], fallback_name: str, framework: str | None = None
|
|
128
|
+
) -> DeploymentResult:
|
|
129
|
+
project_id = str(data.get("id") or "")
|
|
130
|
+
project_name = str(data.get("name") or fallback_name)
|
|
131
|
+
repo_id: int | None = None
|
|
132
|
+
link = data.get("link") or {}
|
|
133
|
+
if isinstance(link, dict):
|
|
134
|
+
rid = link.get("repoId")
|
|
135
|
+
if rid is not None:
|
|
136
|
+
repo_id = int(rid)
|
|
105
137
|
return DeploymentResult(
|
|
106
138
|
provider="vercel",
|
|
107
139
|
project_name=project_name,
|
|
108
140
|
service_id=project_id,
|
|
109
141
|
status="created",
|
|
110
|
-
extra={"framework": framework},
|
|
142
|
+
extra={"framework": framework, "repo_id": repo_id},
|
|
111
143
|
)
|
|
112
144
|
|
|
113
145
|
def set_env(self, project: DeploymentResult, key: str, value: str) -> None:
|
|
@@ -125,15 +157,25 @@ class VercelProvider(DeploymentProvider):
|
|
|
125
157
|
)
|
|
126
158
|
|
|
127
159
|
def deploy(self, project: DeploymentResult, *, ref: str | None = None) -> DeploymentResult:
|
|
160
|
+
git_source: dict[str, Any] = {"type": "github", "ref": ref or "main"}
|
|
161
|
+
repo_id = (project.extra or {}).get("repo_id")
|
|
162
|
+
if repo_id is not None:
|
|
163
|
+
git_source["repoId"] = repo_id
|
|
128
164
|
payload: dict[str, Any] = {
|
|
129
165
|
"name": project.project_name,
|
|
130
166
|
"target": "production",
|
|
131
|
-
"gitSource":
|
|
167
|
+
"gitSource": git_source,
|
|
168
|
+
"projectSettings": {
|
|
169
|
+
"framework": FRAMEWORK_PRESETS.get(
|
|
170
|
+
(project.extra or {}).get("framework", ""),
|
|
171
|
+
(project.extra or {}).get("framework"),
|
|
172
|
+
)
|
|
173
|
+
},
|
|
132
174
|
}
|
|
133
175
|
try:
|
|
134
176
|
data = self.http.request(
|
|
135
177
|
"POST",
|
|
136
|
-
f"{API_BASE}/v13/deployments",
|
|
178
|
+
f"{API_BASE}/v13/deployments?skipAutoDetectionConfirmation=1",
|
|
137
179
|
headers=self._headers(),
|
|
138
180
|
json=payload,
|
|
139
181
|
)
|
|
@@ -152,12 +194,31 @@ class VercelProvider(DeploymentProvider):
|
|
|
152
194
|
extra=project.extra,
|
|
153
195
|
)
|
|
154
196
|
|
|
155
|
-
def
|
|
197
|
+
def _fetch_deployment(self, project: DeploymentResult) -> dict[str, Any] | None:
|
|
198
|
+
service_id = project.service_id
|
|
199
|
+
if service_id.startswith("prj_"):
|
|
200
|
+
data = self.http.request(
|
|
201
|
+
"GET",
|
|
202
|
+
f"{API_BASE}/v6/deployments",
|
|
203
|
+
headers=self._headers(),
|
|
204
|
+
params={"projectId": service_id, "limit": 1},
|
|
205
|
+
)
|
|
206
|
+
deployments = data.get("deployments") if isinstance(data, dict) else None
|
|
207
|
+
if not isinstance(deployments, list) or not deployments:
|
|
208
|
+
return None
|
|
209
|
+
first = deployments[0]
|
|
210
|
+
return first if isinstance(first, dict) else None
|
|
156
211
|
data = self.http.request(
|
|
157
212
|
"GET",
|
|
158
|
-
f"{API_BASE}/v13/deployments/{
|
|
213
|
+
f"{API_BASE}/v13/deployments/{service_id}",
|
|
159
214
|
headers=self._headers(),
|
|
160
215
|
)
|
|
216
|
+
if not isinstance(data, dict):
|
|
217
|
+
raise ProviderError("Vercel returned an unexpected deployment status.")
|
|
218
|
+
return data
|
|
219
|
+
|
|
220
|
+
def get_status(self, project: DeploymentResult) -> DeployStatus:
|
|
221
|
+
data = self._fetch_deployment(project)
|
|
161
222
|
if not isinstance(data, dict):
|
|
162
223
|
raise ProviderError("Vercel returned an unexpected deployment status.")
|
|
163
224
|
state = str(data.get("readyState") or data.get("state") or "QUEUED")
|
|
@@ -188,11 +249,7 @@ class VercelProvider(DeploymentProvider):
|
|
|
188
249
|
return status.url
|
|
189
250
|
|
|
190
251
|
def get_logs(self, project: DeploymentResult) -> list[str]:
|
|
191
|
-
data = self.
|
|
192
|
-
"GET",
|
|
193
|
-
f"{API_BASE}/v13/deployments/{project.service_id}",
|
|
194
|
-
headers=self._headers(),
|
|
195
|
-
)
|
|
252
|
+
data = self._fetch_deployment(project)
|
|
196
253
|
if not isinstance(data, dict):
|
|
197
254
|
return []
|
|
198
255
|
logs: list[str] = []
|
|
@@ -19,10 +19,17 @@ from rich.text import Text
|
|
|
19
19
|
|
|
20
20
|
console = Console()
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
BANNER = r""" ____ _____ ____ _ _____ _______ ___ ____ ____ _____
|
|
23
|
+
| _ \| ____| _ \| | / _ \ \ / / ___/ _ \| _ \ / ___| ____|
|
|
24
|
+
| | | | _| | |_) | | | | | \ V /| |_ | | | | |_) | | _| _|
|
|
25
|
+
| |_| | |___| __/| |__| |_| || | | _|| |_| | _ <| |_| | |___
|
|
26
|
+
|____/|_____|_| |_____\___/ |_| |_| \___/|_| \_\____|_____|"""
|
|
27
|
+
|
|
28
|
+
ART_COLOR = "bold cornflower_blue"
|
|
29
|
+
SWEEP_COLOR = "bold cyan"
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
TAGLINE = "GitHub → Production Automation"
|
|
32
|
+
CREDIT = "built by sifana codex"
|
|
26
33
|
|
|
27
34
|
|
|
28
35
|
def _animation_enabled() -> bool:
|
|
@@ -32,25 +39,13 @@ def _animation_enabled() -> bool:
|
|
|
32
39
|
return bool(console.is_terminal) and not os.environ.get("CI")
|
|
33
40
|
|
|
34
41
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
frames: list[Text] = []
|
|
38
|
-
for i in range(len(HEADER) + 1):
|
|
39
|
-
text = Text()
|
|
40
|
-
text.append(" " * 4)
|
|
41
|
-
text.append(HEADER[:i], style="bold white on blue")
|
|
42
|
-
text.append(HEADER[i:], style="bold dim white")
|
|
43
|
-
frames.append(text)
|
|
44
|
-
flash = Text(" " * 4)
|
|
45
|
-
flash.append(HEADER, style="bold white on red")
|
|
46
|
-
frames.append(flash)
|
|
47
|
-
return frames
|
|
42
|
+
def _banner_lines() -> list[str]:
|
|
43
|
+
return [line.rstrip() for line in BANNER.splitlines() if line.strip()]
|
|
48
44
|
|
|
49
45
|
|
|
50
|
-
def
|
|
46
|
+
def _banner_text(art: str, style: str) -> Text:
|
|
51
47
|
text = Text()
|
|
52
|
-
text.append(
|
|
53
|
-
text.append_text(header)
|
|
48
|
+
text.append(art, style=style)
|
|
54
49
|
text.append("\n\n")
|
|
55
50
|
text.append(TAGLINE, style="bold cyan")
|
|
56
51
|
text.append("\n")
|
|
@@ -58,9 +53,27 @@ def _render_banner_text(header: Text) -> Text:
|
|
|
58
53
|
return text
|
|
59
54
|
|
|
60
55
|
|
|
56
|
+
def _sweep_frames() -> Iterator[Text]:
|
|
57
|
+
"""Sweep a light across the DEPLOYFORGE wordmark, left to right."""
|
|
58
|
+
lines = _banner_lines()
|
|
59
|
+
maxlen = max(len(line) for line in lines)
|
|
60
|
+
for width in range(0, maxlen + 2):
|
|
61
|
+
frame = Text()
|
|
62
|
+
for line in lines:
|
|
63
|
+
frame.append(line[:width], style=SWEEP_COLOR)
|
|
64
|
+
if width < len(line):
|
|
65
|
+
frame.append(line[width:], style="dim")
|
|
66
|
+
frame.append("\n")
|
|
67
|
+
frame.append("\n")
|
|
68
|
+
frame.append(TAGLINE, style="bold cyan")
|
|
69
|
+
frame.append("\n")
|
|
70
|
+
frame.append(CREDIT, style="italic dim")
|
|
71
|
+
yield frame
|
|
72
|
+
|
|
73
|
+
|
|
61
74
|
def _static_banner() -> None:
|
|
62
|
-
|
|
63
|
-
console.print(Panel.fit(
|
|
75
|
+
art = "\n".join(_banner_lines())
|
|
76
|
+
console.print(Panel.fit(_banner_text(art, ART_COLOR), border_style="cyan", padding=(1, 2)))
|
|
64
77
|
|
|
65
78
|
|
|
66
79
|
def banner() -> None:
|
|
@@ -68,11 +81,11 @@ def banner() -> None:
|
|
|
68
81
|
_static_banner()
|
|
69
82
|
return
|
|
70
83
|
try:
|
|
71
|
-
with Live(console=console, refresh_per_second=
|
|
72
|
-
for frame in
|
|
73
|
-
live.update(
|
|
74
|
-
time.sleep(0.
|
|
75
|
-
time.sleep(0.
|
|
84
|
+
with Live(console=console, refresh_per_second=30, transient=True) as live:
|
|
85
|
+
for frame in _sweep_frames():
|
|
86
|
+
live.update(Panel.fit(frame, border_style="cyan", padding=(1, 2)))
|
|
87
|
+
time.sleep(0.018)
|
|
88
|
+
time.sleep(0.18)
|
|
76
89
|
except (OSError, KeyboardInterrupt):
|
|
77
90
|
pass
|
|
78
91
|
console.print()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deployforge
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: From GitHub to a Live Application. Automatically. DeployForge analyzes an application and deploys its frontend and backend to production.
|
|
5
5
|
Author-email: sifuna codex <www.antonysifuna07@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
import pytest
|
|
7
8
|
from typer.testing import CliRunner
|
|
8
9
|
|
|
9
10
|
from deployforge.cli import app
|
|
@@ -103,7 +104,8 @@ def test_status_empty(tmp_path: Path) -> None:
|
|
|
103
104
|
assert "STATUS" in result.output
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
def test_verify_no_urls(tmp_path: Path) -> None:
|
|
107
|
+
def test_verify_no_urls(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
108
|
+
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
|
|
107
109
|
result = runner.invoke(app, ["verify"])
|
|
108
110
|
assert result.exit_code == 0
|
|
109
111
|
assert "No deployed URLs" in result.output or "running" in result.output.lower()
|
|
@@ -89,11 +89,19 @@ class TestVercelProvider:
|
|
|
89
89
|
def test_deploy(self) -> None:
|
|
90
90
|
http = FakeHTTP(
|
|
91
91
|
{
|
|
92
|
-
"https://api.vercel.com/v13/deployments": {
|
|
92
|
+
"https://api.vercel.com/v13/deployments?skipAutoDetectionConfirmation=1": {
|
|
93
|
+
"id": "dpl_1",
|
|
94
|
+
"readyState": "BUILDING",
|
|
95
|
+
},
|
|
93
96
|
}
|
|
94
97
|
)
|
|
95
98
|
provider = VercelProvider(token="tok", client=http)
|
|
96
|
-
project = DeploymentResult(
|
|
99
|
+
project = DeploymentResult(
|
|
100
|
+
provider="vercel",
|
|
101
|
+
project_name="p",
|
|
102
|
+
service_id="prj_1",
|
|
103
|
+
extra={"repo_id": 123},
|
|
104
|
+
)
|
|
97
105
|
deployed = provider.deploy(project, ref="main")
|
|
98
106
|
assert deployed.service_id == "dpl_1"
|
|
99
107
|
assert deployed.status == "building"
|
|
@@ -101,9 +109,13 @@ class TestVercelProvider:
|
|
|
101
109
|
def test_get_status_ready(self) -> None:
|
|
102
110
|
http = FakeHTTP(
|
|
103
111
|
{
|
|
104
|
-
"https://api.vercel.com/
|
|
105
|
-
"
|
|
106
|
-
|
|
112
|
+
"https://api.vercel.com/v6/deployments": {
|
|
113
|
+
"deployments": [
|
|
114
|
+
{
|
|
115
|
+
"readyState": "READY",
|
|
116
|
+
"alias": ["my-app.vercel.app"],
|
|
117
|
+
}
|
|
118
|
+
]
|
|
107
119
|
},
|
|
108
120
|
}
|
|
109
121
|
)
|
|
@@ -116,9 +128,13 @@ class TestVercelProvider:
|
|
|
116
128
|
def test_get_status_error(self) -> None:
|
|
117
129
|
http = FakeHTTP(
|
|
118
130
|
{
|
|
119
|
-
"https://api.vercel.com/
|
|
120
|
-
"
|
|
121
|
-
|
|
131
|
+
"https://api.vercel.com/v6/deployments": {
|
|
132
|
+
"deployments": [
|
|
133
|
+
{
|
|
134
|
+
"readyState": "ERROR",
|
|
135
|
+
"errorMessage": "Build failed",
|
|
136
|
+
}
|
|
137
|
+
]
|
|
122
138
|
},
|
|
123
139
|
}
|
|
124
140
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|