deployforge 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.
Files changed (37) hide show
  1. deployforge/__init__.py +3 -0
  2. deployforge/__version__.py +3 -0
  3. deployforge/analyzer/__init__.py +23 -0
  4. deployforge/analyzer/backend.py +326 -0
  5. deployforge/analyzer/database.py +120 -0
  6. deployforge/analyzer/frontend.py +179 -0
  7. deployforge/analyzer/project.py +389 -0
  8. deployforge/analyzer/shared.py +109 -0
  9. deployforge/cli.py +825 -0
  10. deployforge/config.py +195 -0
  11. deployforge/deployment/__init__.py +19 -0
  12. deployforge/deployment/orchestrator.py +331 -0
  13. deployforge/deployment/planner.py +172 -0
  14. deployforge/deployment/verifier.py +65 -0
  15. deployforge/errors/__init__.py +53 -0
  16. deployforge/github/__init__.py +21 -0
  17. deployforge/github/integration.py +127 -0
  18. deployforge/integration/__init__.py +20 -0
  19. deployforge/integration/cors.py +30 -0
  20. deployforge/integration/environment.py +62 -0
  21. deployforge/integration/frontend_backend.py +39 -0
  22. deployforge/providers/__init__.py +32 -0
  23. deployforge/providers/base.py +151 -0
  24. deployforge/providers/render.py +218 -0
  25. deployforge/providers/vercel.py +205 -0
  26. deployforge/security/__init__.py +4 -0
  27. deployforge/security/gitignore.py +35 -0
  28. deployforge/security/scanner.py +125 -0
  29. deployforge/security/secrets.py +110 -0
  30. deployforge/ui/__init__.py +1 -0
  31. deployforge/ui/terminal.py +151 -0
  32. deployforge-0.1.0.dist-info/METADATA +218 -0
  33. deployforge-0.1.0.dist-info/RECORD +37 -0
  34. deployforge-0.1.0.dist-info/WHEEL +5 -0
  35. deployforge-0.1.0.dist-info/entry_points.txt +2 -0
  36. deployforge-0.1.0.dist-info/licenses/LICENSE +21 -0
  37. deployforge-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,125 @@
1
+ """Security preflight scanner.
2
+
3
+ Scans a project for sensitive files, secret patterns, and high-entropy
4
+ assignments before a deployment is attempted. Secret values are never
5
+ included in findings — only non-reversible fingerprints.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+
13
+ from deployforge.analyzer.shared import walk_files
14
+ from deployforge.security.secrets import (
15
+ HIGH_ENTROPY_RE,
16
+ SECRET_PATTERNS,
17
+ Finding,
18
+ fingerprint,
19
+ is_high_entropy,
20
+ is_sensitive_path,
21
+ )
22
+
23
+ IGNORED_SCAN_DIRS = {".git", ".github", "node_modules", ".venv", "__pycache__", "dist", "build"}
24
+
25
+
26
+ @dataclass
27
+ class SecurityReport:
28
+ findings: list[Finding]
29
+ files_scanned: int
30
+ sensitive_files: list[str] = field(default_factory=list)
31
+ gitignore_ok: bool = True
32
+ gitignore_missing: list[str] = field(default_factory=list)
33
+
34
+ @property
35
+ def high_confidence(self) -> bool:
36
+ return any(f.confidence == "high" for f in self.findings)
37
+
38
+ @property
39
+ def blocked(self) -> bool:
40
+ return self.high_confidence
41
+
42
+
43
+ def _scan_file(path: Path, relative_to: Path, strict: bool) -> list[Finding]:
44
+ try:
45
+ text = path.read_text(encoding="utf-8", errors="ignore")
46
+ except OSError:
47
+ return []
48
+ rel = path.relative_to(relative_to).as_posix()
49
+ findings: list[Finding] = []
50
+
51
+ if is_sensitive_path(rel):
52
+ findings.append(Finding(name="Sensitive file", file=rel, line=None, confidence="high"))
53
+
54
+ for line_no, line in enumerate(text.splitlines(), start=1):
55
+ if not line.strip():
56
+ continue
57
+ for pattern, name in SECRET_PATTERNS:
58
+ match = pattern.search(line)
59
+ if match:
60
+ value = match.group(0)
61
+ if "PRIVATE KEY" in name:
62
+ value = "-----BEGIN " + "PRIVATE KEY-----"
63
+ findings.append(
64
+ Finding(
65
+ name=name,
66
+ file=rel,
67
+ line=line_no,
68
+ confidence="high",
69
+ value_fingerprint=fingerprint(value),
70
+ )
71
+ )
72
+ break
73
+ match = HIGH_ENTROPY_RE.search(line)
74
+ if match:
75
+ value = match.group("value")
76
+ if value and is_high_entropy(value):
77
+ confidence = "high" if strict else "medium"
78
+ findings.append(
79
+ Finding(
80
+ name="High-entropy assignment",
81
+ file=rel,
82
+ line=line_no,
83
+ confidence=confidence,
84
+ value_fingerprint=fingerprint(value),
85
+ )
86
+ )
87
+ return findings
88
+
89
+
90
+ def scan_project(root: Path, level: str = "normal") -> SecurityReport:
91
+ """Scan *root* and return a security report.
92
+
93
+ ``level`` is one of ``"off"``, ``"normal"``, ``"strict"``.
94
+ """
95
+ normal = level != "off"
96
+ strict = level == "strict"
97
+ findings: list[Finding] = []
98
+ sensitive_files: list[str] = []
99
+ files_scanned = 0
100
+
101
+ from deployforge.security.gitignore import validate_gitignore
102
+
103
+ gitignore_ok, gitignore_missing = validate_gitignore(root)
104
+
105
+ for path in walk_files(root):
106
+ if any(part in IGNORED_SCAN_DIRS for part in path.parts):
107
+ continue
108
+ if path.name in {".gitignore"}:
109
+ continue
110
+ files_scanned += 1
111
+ if not normal:
112
+ continue
113
+ file_findings = _scan_file(path, root, strict)
114
+ findings.extend(file_findings)
115
+ for f in file_findings:
116
+ if f.name == "Sensitive file":
117
+ sensitive_files.append(f.file)
118
+
119
+ return SecurityReport(
120
+ findings=findings,
121
+ files_scanned=files_scanned,
122
+ sensitive_files=sorted(set(sensitive_files)),
123
+ gitignore_ok=gitignore_ok,
124
+ gitignore_missing=gitignore_missing,
125
+ )
@@ -0,0 +1,110 @@
1
+ """Secret patterns and sensitive-file rules for the security preflight."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import re
7
+ from dataclasses import dataclass
8
+
9
+ # Files that should never reach a repository or deployment configuration.
10
+ SENSITIVE_FILENAMES = (
11
+ ".env",
12
+ ".env.local",
13
+ ".env.production",
14
+ ".env.development",
15
+ ".env.test",
16
+ "credentials.json",
17
+ "service-account.json",
18
+ "client_secret.json",
19
+ "id_rsa",
20
+ "id_ed25519",
21
+ "id_dsa",
22
+ "id_ecdsa",
23
+ ".netrc",
24
+ ".npmrc",
25
+ "dockerconfigjson",
26
+ )
27
+
28
+ SENSITIVE_EXTENSIONS = (".pem", ".key", ".p12", ".pfx", ".p8", ".keystore")
29
+
30
+ SENSITIVE_SUFFIXES = ("_rsa", "_dsa", "_ed25519", "_ecdsa")
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class Finding:
35
+ name: str
36
+ file: str
37
+ line: int | None
38
+ confidence: str
39
+ value_fingerprint: str | None = None
40
+
41
+
42
+ _AUTH = r"[^/\s@]+:[^@\s]+@"
43
+
44
+
45
+ def fingerprint(value: str) -> str:
46
+ digest = hashlib.sha256(value.encode("utf-8", errors="ignore")).hexdigest()
47
+ return digest[:10]
48
+
49
+
50
+ # (pattern, name)
51
+ SECRET_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
52
+ (re.compile(r"AKIA[0-9A-Z]{16}"), "AWS access key"),
53
+ (re.compile(r"AIza[0-9A-Za-z\-_]{35}", re.IGNORECASE), "Google API key"),
54
+ (re.compile(r"ghp_[A-Za-z0-9]{36}"), "GitHub personal access token"),
55
+ (re.compile(r"gho_[A-Za-z0-9]{36}"), "GitHub OAuth token"),
56
+ (re.compile(r"github_pat_[A-Za-z0-9_]{22,}"), "GitHub fine-grained token"),
57
+ (re.compile(r"xox[baprs]-[A-Za-z0-9\-]{10,}"), "Slack token"),
58
+ (re.compile(r"sk_live_[0-9A-Za-z]{24,}"), "Stripe live secret key"),
59
+ (re.compile(r"sk_test_[0-9A-Za-z]{24,}"), "Stripe test secret key"),
60
+ (re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----"), "Private key"),
61
+ (
62
+ re.compile(rf"postgres(?:ql)?://{_AUTH}"),
63
+ "Database connection string",
64
+ ),
65
+ (re.compile(rf"mongo(db|\+srv)?://{_AUTH}"), "MongoDB connection string"),
66
+ (re.compile(rf"redis://{_AUTH}"), "Redis connection string"),
67
+ (
68
+ re.compile(
69
+ r"(?i)(?P<key>aws_secret_access_key|secret_key|api_secret|client_secret|"
70
+ r"jwt_secret|private_key)\s*[=:]\s*[\"']?[A-Za-z0-9+/=_\-]{16,}[\"']?"
71
+ ),
72
+ "Secret assignment",
73
+ ),
74
+ )
75
+
76
+ # High-entropy token assignments (>= 24 mixed-case chars plus digits).
77
+ HIGH_ENTROPY_RE = re.compile(
78
+ r"(?i)((?P<key>api[_\-]?key|token|password|secret))\s*[=:]\s*"
79
+ r"[\"'](?P<value>[A-Za-z0-9+/=_\-]{24,})[\"']"
80
+ )
81
+
82
+ _MIXED_CASE_AND_DIGIT = re.compile(r"[A-Z].*[a-z].*[0-9]|[a-z].*[A-Z].*[0-9]|[0-9].*[A-Za-z]")
83
+
84
+
85
+ def is_high_entropy(value: str) -> bool:
86
+ """Guard against colored hex/counts: require mixed case *and* digits."""
87
+ return bool(_MIXED_CASE_AND_DIGIT.match(value)) and sum(c.isdigit() for c in value) >= 2
88
+
89
+
90
+ def is_sensitive_filename(name: str) -> bool:
91
+ lower = name.lower()
92
+ if lower in SENSITIVE_FILENAMES:
93
+ return True
94
+ if lower.endswith(SENSITIVE_EXTENSIONS):
95
+ return True
96
+ return casefold_suffix(lower)
97
+
98
+
99
+ def casefold_suffix(lower: str) -> bool:
100
+ return any(lower.endswith(suffix) for suffix in SENSITIVE_SUFFIXES)
101
+
102
+
103
+ def is_sensitive_path(path: str) -> bool:
104
+ """True for file names or extensions that should never be deployed."""
105
+ lower = path.lower()
106
+ if any(part in lower for part in (".env",)) and lower.split("/")[-1].startswith(".env"):
107
+ return True
108
+ if is_sensitive_filename(path.split("/")[-1]):
109
+ return True
110
+ return lower.endswith(SENSITIVE_EXTENSIONS)
@@ -0,0 +1 @@
1
+ from deployforge.ui.terminal import * # noqa: F401,F403
@@ -0,0 +1,151 @@
1
+ """Terminal UI helpers for DeployForge built on Rich.
2
+
3
+ All rendering goes through this module so the surface can be replaced without
4
+ touching the rest of the codebase. Works on every OS; animation is skipped
5
+ automatically on non-TTY output (CI, pipes, redirects).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import time
12
+ from collections.abc import Iterator
13
+ from contextlib import contextmanager
14
+
15
+ from rich.console import Console
16
+ from rich.live import Live
17
+ from rich.panel import Panel
18
+ from rich.text import Text
19
+
20
+ console = Console()
21
+
22
+ TAGLINE = "GitHub → Production Automation"
23
+ CREDIT = "built by sifuna codex"
24
+
25
+ HEADER = "DEPLOYFORGE"
26
+
27
+
28
+ def _animation_enabled() -> bool:
29
+ flag = os.environ.get("DEPLOYFORGE_NO_ANIMATION")
30
+ if flag and flag.strip().lower() in {"1", "true", "yes", "on"}:
31
+ return False
32
+ return bool(console.is_terminal) and not os.environ.get("CI")
33
+
34
+
35
+ def _banner_frames() -> list[Text]:
36
+ """Build the DEPLOYFORGE reveal frames (letters sweep in one by one)."""
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
48
+
49
+
50
+ def _render_banner_text(header: Text) -> Text:
51
+ text = Text()
52
+ text.append("\n")
53
+ text.append_text(header)
54
+ text.append("\n\n")
55
+ text.append(TAGLINE, style="bold cyan")
56
+ text.append("\n")
57
+ text.append(CREDIT, style="italic dim")
58
+ return text
59
+
60
+
61
+ def _static_banner() -> None:
62
+ text = _render_banner_text(Text(" " * 4 + HEADER, style="bold white on blue"))
63
+ console.print(Panel.fit(text, border_style="blue", padding=(1, 3)))
64
+
65
+
66
+ def banner() -> None:
67
+ if not _animation_enabled():
68
+ _static_banner()
69
+ return
70
+ try:
71
+ with Live(console=console, refresh_per_second=24, transient=True) as live:
72
+ for frame in _banner_frames():
73
+ live.update(_render_banner_text(frame))
74
+ time.sleep(0.045)
75
+ time.sleep(0.15)
76
+ except (OSError, KeyboardInterrupt):
77
+ pass
78
+ console.print()
79
+ _static_banner()
80
+
81
+
82
+ def section(title: str) -> None:
83
+ console.print()
84
+ console.rule(title, style="blue")
85
+ console.print()
86
+
87
+
88
+ def ok(message: str) -> None:
89
+ console.print(f"[bold green]\u2713[/bold green] {message}")
90
+
91
+
92
+ def warn(message: str) -> None:
93
+ console.print(f"[bold yellow]\u26a0[/bold yellow] {message}")
94
+
95
+
96
+ def info(message: str) -> None:
97
+ console.print(f"[dim]\u203a[/dim] {message}")
98
+
99
+
100
+ def step(message: str) -> None:
101
+ console.print(f"[bold]{message}[/bold]")
102
+
103
+
104
+ def note(message: str) -> None:
105
+ console.print(f"[dim]{message}[/dim]")
106
+
107
+
108
+ def error(message: str, hint: str | None = None) -> None:
109
+ console.print(f"[bold red]\u2717[/bold red] {message}")
110
+ if hint:
111
+ console.print(hint)
112
+
113
+
114
+ def link(url: str) -> None:
115
+ console.print(url, style="bold underline cyan")
116
+
117
+
118
+ def fail(message: str, hint: str | None = None) -> None:
119
+ console.print(f"[bold red]\u2717[/bold red] {message}")
120
+ if hint:
121
+ console.print(hint)
122
+
123
+
124
+ @contextmanager
125
+ def spinner(message: str) -> Iterator[None]:
126
+ """Render a spinner while a blocking operation runs."""
127
+ with console.status(f"[bold]{message}[/bold]", spinner="dots12"):
128
+ yield
129
+
130
+
131
+ def prompt_yes_no(message: str, default: bool = True) -> bool:
132
+ suffix = " [Y/n]" if default else " [y/N]"
133
+ try:
134
+ answer = input(f"{message}{suffix} ")
135
+ except (EOFError, KeyboardInterrupt):
136
+ return default
137
+ cleaned = answer.strip().lower()
138
+ if not cleaned:
139
+ return default
140
+ return cleaned in ("y", "yes")
141
+
142
+
143
+ def live_panel(title: str, lines: list[tuple[str, str]]) -> None:
144
+ """Render the final live-application summary panel."""
145
+ text = Text()
146
+ text.append(title, style="bold white on blue")
147
+ text.append("\n\n")
148
+ for label, value in lines:
149
+ text.append(f"{label}\n", style="bold")
150
+ text.append(f"{value}\n\n", style="cyan")
151
+ console.print(Panel.fit(text, border_style="blue", padding=(1, 2)))
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: deployforge
3
+ Version: 0.1.0
4
+ Summary: From GitHub to a Live Application. Automatically. DeployForge analyzes an application and deploys its frontend and backend to production.
5
+ Author-email: sifuna codex <www.antonysifuna07@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aasz253/DeployForge
8
+ Project-URL: Repository, https://github.com/aasz253/DeployForge
9
+ Project-URL: Documentation, https://github.com/aasz253/DeployForge#readme
10
+ Project-URL: Issues, https://github.com/aasz253/DeployForge/issues
11
+ Keywords: deploy,vercel,render,devsecops,cli,automation,github
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: System :: Installation/Setup
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typer<1.0,>=0.12
27
+ Requires-Dist: rich<15.0,>=13.7
28
+ Requires-Dist: requests<3.0,>=2.31
29
+ Requires-Dist: PyYAML<7.0,>=6.0
30
+ Requires-Dist: keyring<26.0,>=24.3
31
+ Requires-Dist: platformdirs<5.0,>=4.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
34
+ Requires-Dist: pytest-cov<6.0,>=5.0; extra == "dev"
35
+ Requires-Dist: ruff<1.0,>=0.5; extra == "dev"
36
+ Requires-Dist: mypy<2.0,>=1.10; extra == "dev"
37
+ Requires-Dist: types-PyYAML>=6.0; extra == "dev"
38
+ Requires-Dist: bandit<2.0,>=1.7; extra == "dev"
39
+ Requires-Dist: build<2.0,>=1.2; extra == "dev"
40
+ Requires-Dist: twine<6.0,>=5.0; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # DeployForge
44
+
45
+ **From GitHub to production. Automatically.**
46
+
47
+ DeployForge is the second stage of the PushForge → DeployForge workflow.
48
+ PushForge handles **Local → GitHub**, while DeployForge handles **GitHub → Production** (frontend → Vercel, backend → Render).
49
+
50
+ ---
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install deployforge
56
+ ```
57
+
58
+ Or with [pipx](https://pypa.github.io/pipx/) (recommended):
59
+
60
+ ```bash
61
+ pipx install deployforge
62
+ ```
63
+
64
+ DeployForge is a single, cross-platform CLI — it works on Windows, macOS, and Linux.
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ ```bash
71
+ # 1. Push your code with PushForge
72
+ pushforge
73
+
74
+ # 2. Deploy with DeployForge
75
+ deployforge
76
+ ```
77
+
78
+ DeployForge automatically:
79
+ 1. Analyzes your project structure (Next.js, Vite, FastAPI, Express, Django…)
80
+ 2. Creates a deployment plan (what goes to Vercel, what goes to Render)
81
+ 3. Runs security preflight checks (scans for leaked secrets)
82
+ 4. Deploys backend → Render, frontend → Vercel
83
+ 5. Wires environment variables (`NEXT_PUBLIC_API_URL`, `FRONTEND_URL`, `CORS_ORIGIN`)
84
+ 6. Sets up CORS on the backend
85
+ 7. Verifies all deployments and prints live URLs
86
+
87
+ ---
88
+
89
+ ## Commands
90
+
91
+ | Command | Description |
92
+ |---------|-------------|
93
+ | `deployforge` | Full automated deployment (analyze → deploy → verify) |
94
+ | `deployforge init` | Create `.deployforge/config.yml` for a project |
95
+ | `deployforge analyze` | Analyze project structure and show deployment plan |
96
+ | `deployforge deploy` | Deploy directly (with flags `--dry-run`, `--debug`) |
97
+ | `deployforge status` | Show status of deployed services |
98
+ | `deployforge verify` | Verify all deployed URLs are reachable and healthy |
99
+ | `deployforge security` | Run security preflight scan |
100
+ | `deployforge doctor` | Diagnose environment and credentials |
101
+ | `deployforge config` | Show resolved configuration |
102
+ | `deployforge logs` | Show recent deployment history |
103
+ | `deployforge version` | Print version |
104
+
105
+ ---
106
+
107
+ ## Options
108
+
109
+ ```
110
+ --dry-run Show what DeployForge would do without making changes
111
+ --non-interactive Skip confirmations (CI-friendly)
112
+ --skip-security Disable the security preflight gate
113
+ --debug Show debug detail for troubleshooting
114
+ --timeout INT Deployment wait timeout in seconds (default: 900)
115
+ --plan TEXT Render plan: free | starter | pro (default: starter)
116
+ ```
117
+
118
+ ---
119
+
120
+ ## How It Works with PushForge
121
+
122
+ ```
123
+ +-------------------+ +-------------------+ +-------------------+
124
+ | Your Machine | | GitHub | | Production |
125
+ | | | | | |
126
+ | PushForge ───────┼────►│ Source Code │ | Vercel (frontend)│
127
+ | Local → GitHub │ | │ | Render (backend) │
128
+ | | │ DeployForge ─────┼────►│ Live URLs │
129
+ +-------------------+ | GitHub → Prod | +-------------------+
130
+ +-------------------+
131
+ ```
132
+
133
+ **You don't need PushForge installed** to use DeployForge — it detects Git repositories automatically. But the two tools are designed to work together seamlessly.
134
+
135
+ ---
136
+
137
+ ## Authentication
138
+
139
+ DeployForge reads provider tokens from environment variables (in order):
140
+
141
+ | Provider | Environment Variables |
142
+ |----------|----------------------|
143
+ | Vercel | `DEPLOYFORGE_VERCEL_TOKEN`, `VERCEL_TOKEN` |
144
+ | Render | `DEPLOYFORGE_RENDER_API_KEY`, `RENDER_API_KEY` |
145
+
146
+ Tokens can also be stored securely in your OS keyring via:
147
+
148
+ ```bash
149
+ deployforge config --set vercel_token
150
+ deployforge config --set render_api_key
151
+ ```
152
+
153
+ > Credentials are **never** stored in project files or printed in logs. Only SHA-256 fingerprints are recorded.
154
+
155
+ ---
156
+
157
+ ## Configuration
158
+
159
+ DeployForge creates `.deployforge/config.yml` when you run `deployforge init`:
160
+
161
+ ```yaml
162
+ frontend:
163
+ provider: vercel
164
+ directory: frontend
165
+
166
+ backend:
167
+ provider: render
168
+ runtime: python
169
+ directory: backend
170
+ ```
171
+
172
+ Environment variables from `.env` files are automatically wired:
173
+ - `NEXT_PUBLIC_API_URL` → set to the backend Render URL
174
+ - `FRONTEND_URL` → set to the frontend Vercel URL (on the backend)
175
+ - CORS is configured automatically
176
+
177
+ ---
178
+
179
+ ## Security
180
+
181
+ DeployForge runs a security preflight before deployment:
182
+ - Scans for API keys, tokens, passwords in tracked files
183
+ - Blocks deployment if high-entropy strings or credential patterns are found (configurable)
184
+ - Never transmits or stores secrets — only fingerprints them locally
185
+
186
+ Disable with `--skip-security` for trusted codebases.
187
+
188
+ ---
189
+
190
+ ## Development
191
+
192
+ ```bash
193
+ # Clone and set up
194
+ git clone https://github.com/aasz253/DeployForge.git
195
+ cd DeployForge
196
+ python -m venv .venv && source .venv/bin/activate
197
+ pip install -e ".[dev]"
198
+
199
+ # Run tests
200
+ pytest
201
+
202
+ # Lint & type-check
203
+ ruff check src tests
204
+ mypy src
205
+
206
+ # Format
207
+ ruff format src tests
208
+ ```
209
+
210
+ ---
211
+
212
+ ## License
213
+
214
+ MIT — see [LICENSE](LICENSE).
215
+
216
+ ## Security Policy
217
+
218
+ See [SECURITY.md](SECURITY.md) for credential handling and disclosure.
@@ -0,0 +1,37 @@
1
+ deployforge/__init__.py,sha256=UleEE3Slnrtj03JlPKTgi5L4JStQK6475F8KmLtDXJ0,95
2
+ deployforge/__version__.py,sha256=RMGMLDY_b6QXh8Ibh7InaQt9BEYanxRRitNsAAuI1yA,62
3
+ deployforge/cli.py,sha256=9CRr6tYSXi_LPbmU5OzzWIkKzlgc1QMs7BRctVNiQJU,28702
4
+ deployforge/config.py,sha256=jjjBdBIkerYWoNCPR7JdYpLhKOeHCamCF3f7wdKNrYQ,6190
5
+ deployforge/analyzer/__init__.py,sha256=LW7FdviG53Os_zn_pOXQveA9mi68pNHAKUObkyT5NGg,604
6
+ deployforge/analyzer/backend.py,sha256=gdoxF-aag6t5-_YIe4Zz5n0GzZB2uKbSRBLSmAK8BbY,10218
7
+ deployforge/analyzer/database.py,sha256=BMBAypd9src4GJIWFno_l8AqzA32rjS9bk8Ob88ciDo,3520
8
+ deployforge/analyzer/frontend.py,sha256=MnQJd6riva84eHD7TtFz2qxB1ZG4xhkrGqwwqmCllS4,5386
9
+ deployforge/analyzer/project.py,sha256=KXTRs2kpIEo6zjRR7o5MJj9fOVnYUwRqklkd2owa1IE,12691
10
+ deployforge/analyzer/shared.py,sha256=UR3QRaLHaG7u4aOlLPhZ2zoSqJCRcRXa-G33DGvzC9w,3112
11
+ deployforge/deployment/__init__.py,sha256=YVK69Qwig_gO6gzfEm4wYNFXgVhzUR9T1N06ppaGjhI,464
12
+ deployforge/deployment/orchestrator.py,sha256=vM88ON8546nTQ-v32Z27Ub6yxaWpfyPLd2YkqpWR5U8,11184
13
+ deployforge/deployment/planner.py,sha256=_yXHzdDRGMUDZCU9EwHhjxlraorD3Qf8A2KodNb7bbk,4997
14
+ deployforge/deployment/verifier.py,sha256=TaO0CoA6c8NfuILa4_zF0Cnv6Nc7s59cCfOe9ynv9-c,1994
15
+ deployforge/errors/__init__.py,sha256=0xXqOIJGBxDmfOtXXIeNclJT9UvRepndS4OWXbCzeIU,1511
16
+ deployforge/github/__init__.py,sha256=nkMppkcIUTrFyuTv5OreVQ3yxMhHq3fAcMosRqlqoaA,378
17
+ deployforge/github/integration.py,sha256=j3r_7b9WxSMdPhQEBF1t8LlOGIk8sx21ICoU2Flq_3A,3716
18
+ deployforge/integration/__init__.py,sha256=MwFZhxry-tOlQot7h3a_qgJameyp_N9dCA7qlPdeXrg,494
19
+ deployforge/integration/cors.py,sha256=oPrbTHO0EP5ak7nJ2Ff3aVuiihRklEfGEZpPw0qijGU,1072
20
+ deployforge/integration/environment.py,sha256=SgMdqoIYcKITtCQTGxdWmrQ9NyIz6FaA0fsdnWs8ZgQ,1734
21
+ deployforge/integration/frontend_backend.py,sha256=bqLfwS--uJboU-0d9LlY12itZs9LqGF1Rqr0OQSMmQ0,1359
22
+ deployforge/providers/__init__.py,sha256=4N_SGEdWnKvnKcYZMEif0zN4PQz53nvGXShSq0o9lAQ,728
23
+ deployforge/providers/base.py,sha256=cXcWyA4YzGLLyOlevXXtZ6gMFH6QDgpcqac1LYOw7qM,4701
24
+ deployforge/providers/render.py,sha256=PAeBD_uk98keyXevU7RXc8E5Z7fr0b1F95QP_85CflU,8121
25
+ deployforge/providers/vercel.py,sha256=R5Swt9M0bsfvt_o7YB-UF2LQsnwzZcZK5giKefvg0ZU,7140
26
+ deployforge/security/__init__.py,sha256=AlJDJeGzbDELlx9yoPsFcFmoUT677OiWpeapNnbNgWU,204
27
+ deployforge/security/gitignore.py,sha256=Ys4ea-kMF75CTMwx8r2aclK7aypcqu5KsNdZ-JeFgeg,1015
28
+ deployforge/security/scanner.py,sha256=358i3yoG1ubsRblqlozqxUBjYfhmkPdExpM2u4kMup0,3949
29
+ deployforge/security/secrets.py,sha256=YKmp0cV5zPSOAg9SNNJGHJbwXJm4fxj_JzUeEIwvuoY,3483
30
+ deployforge/ui/__init__.py,sha256=hrMVtuRKykMiVJ9zgF0Xa9qD6IxAEDKkAa0SyW_aN-o,57
31
+ deployforge/ui/terminal.py,sha256=QMxLlLX8lsYNFXz9FQ8rKkD_4m0bfvLzaIkpYuAC3rU,4212
32
+ deployforge-0.1.0.dist-info/licenses/LICENSE,sha256=dbm8YZQMr1BMBNJFL46Ori447BiqXWmfxkwLxb5jT8w,1068
33
+ deployforge-0.1.0.dist-info/METADATA,sha256=nZmPVrU7ofZY4PvTUUEBmjP-_3W36I1yVV23dHW72gk,6814
34
+ deployforge-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
35
+ deployforge-0.1.0.dist-info/entry_points.txt,sha256=GkQxyLtktbLJG85EMMV_nQSeba_G57HtoysUfs5-m5I,54
36
+ deployforge-0.1.0.dist-info/top_level.txt,sha256=6kmVDrN44FwZopoBzn1x1KhftUMTOFlJFvg0xCJBdUI,12
37
+ deployforge-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ deployforge = deployforge.cli:entry
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 sifuna codex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ deployforge