tw-framework 0.2.3__tar.gz → 0.2.4__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.
Files changed (64) hide show
  1. tw_framework-0.2.4/PKG-INFO +101 -0
  2. {tw_framework-0.2.3 → tw_framework-0.2.4}/pyproject.toml +3 -2
  3. tw_framework-0.2.4/tw_framework/adapters/__init__.py +3 -0
  4. tw_framework-0.2.4/tw_framework/adapters/cloudflare.py +96 -0
  5. tw_framework-0.2.4/tw_framework/adapters/netlify.py +91 -0
  6. tw_framework-0.2.4/tw_framework/adapters/vercel.py +192 -0
  7. tw_framework-0.2.4/tw_framework/adapters/vercel_functions.py +554 -0
  8. tw_framework-0.2.4/tw_framework.egg-info/PKG-INFO +101 -0
  9. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework.egg-info/SOURCES.txt +6 -1
  10. tw_framework-0.2.3/PKG-INFO +0 -8
  11. tw_framework-0.2.3/tw_framework.egg-info/PKG-INFO +0 -8
  12. {tw_framework-0.2.3 → tw_framework-0.2.4}/README.md +0 -0
  13. {tw_framework-0.2.3 → tw_framework-0.2.4}/setup.cfg +0 -0
  14. {tw_framework-0.2.3 → tw_framework-0.2.4}/tests/test_deployment.py +0 -0
  15. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/__init__.py +0 -0
  16. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/advanced_diagnostics.py +0 -0
  17. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/asset_optimizer.py +0 -0
  18. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/ast_nodes.py +0 -0
  19. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/build.py +0 -0
  20. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/build_performance.py +0 -0
  21. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/build_report.py +0 -0
  22. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/cli.py +0 -0
  23. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/code_splitting.py +0 -0
  24. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/common.py +0 -0
  25. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/compiler.py +0 -0
  26. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/compiler_stats.py +0 -0
  27. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/dead_code.py +0 -0
  28. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/dependency_graph.py +0 -0
  29. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/deploy.py +0 -0
  30. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/dev.py +0 -0
  31. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/diagnostics.py +0 -0
  32. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/dynamic_imports.py +0 -0
  33. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/error_formatter.py +0 -0
  34. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/framework.py +0 -0
  35. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/hydration.py +0 -0
  36. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/incremental_cache.py +0 -0
  37. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/interpreter.py +0 -0
  38. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/ir.py +0 -0
  39. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/lexer.py +0 -0
  40. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/lowering.py +0 -0
  41. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/parser.py +0 -0
  42. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/partial_rebuild.py +0 -0
  43. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/performance_analyzer.py +0 -0
  44. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/plugin_runtime.py +0 -0
  45. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/production_optimizer.py +0 -0
  46. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/reactivity.py +0 -0
  47. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/render_css.py +0 -0
  48. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/render_html.py +0 -0
  49. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/route_optimizer.py +0 -0
  50. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/router.py +0 -0
  51. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/runtime_model.py +0 -0
  52. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/runtime_values.py +0 -0
  53. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/semantic.py +0 -0
  54. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/server.py +0 -0
  55. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/signature.py +0 -0
  56. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/static_dynamic_auto.py +0 -0
  57. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/streaming.py +0 -0
  58. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/test_build_cli_pytest.py +0 -0
  59. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/tree_shaking.py +0 -0
  60. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework/twm_parser.py +0 -0
  61. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework.egg-info/dependency_links.txt +0 -0
  62. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework.egg-info/entry_points.txt +0 -0
  63. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework.egg-info/requires.txt +0 -0
  64. {tw_framework-0.2.3 → tw_framework-0.2.4}/tw_framework.egg-info/top_level.txt +0 -0
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: tw-framework
3
+ Version: 0.2.4
4
+ Summary: TW framework CLI with create, dev, build, and deploy commands
5
+ Author: TW Framework
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=7; extra == "dev"
10
+
11
+ # TW Framework
12
+
13
+ TW is a custom language + framework for building websites with a Next.js‑like developer experience.
14
+
15
+ ## Zero‑Config Deployment
16
+
17
+ 1. Create a GitHub repository with your TW project (containing `tw.config`, `pages/`, `components/`, `public/`).
18
+ 2. Connect the repository to Vercel or Netlify.
19
+ 3. The platform automatically detects `tw.config` and runs `tw build`.
20
+ 4. Your site goes live – no terminal commands required.
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # Create a new project
26
+ tw init my-site
27
+ cd my-site
28
+
29
+ # Development server
30
+ tw dev
31
+
32
+ # Production build
33
+ tw build
34
+
35
+ # Preview the build
36
+ tw preview
37
+ ```
38
+
39
+ ## CLI Commands
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `tw init [name]` | Create a new TW project in the current directory |
44
+ | `tw dev` | Start the development server with live reload |
45
+ | `tw build` | Generate a production build |
46
+ | `tw preview` | Preview the built output |
47
+ | `tw export` | Generate a static export |
48
+ | `tw deploy` | Build and deploy to a hosting provider |
49
+ | `tw doctor` | Run project health checks and deployment compatibility |
50
+ | `tw ast file.tw` | Print the AST JSON for a TW source file |
51
+ | `tw ir file.tw` | Print the IR JSON for a TW source file |
52
+ | `tw run file.tw` | Interpret a TW file and output HTML |
53
+ | `tw check file.tw` | Print diagnostics for a TW file |
54
+ | `tw tokens file.tw` | Print the token stream JSON for a TW source file |
55
+ | `tw clean` | Clean dist and hidden cache folders |
56
+ | `tw info` | Show a project summary |
57
+ | `tw login` | Save deploy provider configuration |
58
+
59
+ ## Deployment Metadata
60
+
61
+ After a build, the file `dist/tw.deploy.json` is automatically generated. It contains:
62
+
63
+ ```json
64
+ {
65
+ "framework": "tw",
66
+ "version": "1.0.0",
67
+ "build": "tw build",
68
+ "output": "dist",
69
+ "runtime": "ssr"
70
+ }
71
+ ```
72
+
73
+ Deployment platforms can read this file to detect the framework and configure the build.
74
+
75
+ ## Adapters
76
+
77
+ TW Framework includes built‑in adapters for Vercel and Netlify. They provide:
78
+
79
+ - Framework detection (`detect()` function)
80
+ - Automatic generation of `vercel.json` / `netlify.toml` when missing
81
+ - Build command, output directory, install command, and SSR handling
82
+
83
+ ## Documentation
84
+
85
+ - [Getting Started](docs/getting-started.md)
86
+ - [Deployment to Vercel](docs/deployment/vercel.md)
87
+ - [Deployment to Netlify](docs/deployment/netlify.md)
88
+
89
+ ## Testing
90
+
91
+ ```bash
92
+ python3 -m unittest discover -s tests -q
93
+ python3 run_tests.py
94
+ ```
95
+
96
+ ## Debug Flags
97
+
98
+ - `TW_WARN_LITERAL_PARSE=1` – warn when literal parsing falls back to string
99
+ - `TW_STRICT_EVAL=1` – re‑raise runtime expression errors instead of warning
100
+
101
+ These flags are mainly useful while debugging parser/runtime edge cases or tightening CI checks around expression handling.
@@ -4,8 +4,9 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tw-framework"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "TW framework CLI with create, dev, build, and deploy commands"
9
+ readme = "README.md"
9
10
  requires-python = ">=3.9"
10
11
  authors = [
11
12
  { name = "TW Framework" }
@@ -22,4 +23,4 @@ dev = [
22
23
  tw = "tw_framework.cli:main"
23
24
 
24
25
  [tool.setuptools]
25
- packages = ["tw_framework"]
26
+ packages = ["tw_framework", "tw_framework.adapters"]
@@ -0,0 +1,3 @@
1
+ from .vercel import generate_vercel_output
2
+ from .netlify import generate_netlify_output
3
+ from .cloudflare import generate_cloudflare_output
@@ -0,0 +1,96 @@
1
+ """
2
+ Cloudflare adapter for TWLang framework.
3
+ Generates _routes.json and _worker.js for Cloudflare Pages.
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Dict, Any
9
+
10
+ def generate_cloudflare_output(
11
+ dist_dir: str,
12
+ config: Dict[str, Any],
13
+ project_root: str,
14
+ ) -> None:
15
+ """
16
+ Generate Cloudflare deployment files from build output.
17
+
18
+ Args:
19
+ dist_dir: Path to the build output directory (e.g., 'dist').
20
+ config: JSON to tw.config as a dictionary.
21
+ project_root: Root directory of the project.
22
+ """
23
+ root = Path(project_root)
24
+ routes_path = root / "_routes.json"
25
+ worker_path = root / "_worker.js"
26
+
27
+ # Build _routes.json
28
+ routes = []
29
+ # Add redirects
30
+ redirects = config.get("redirects", {}).get("rule", [])
31
+ for rule in redirects:
32
+ source = rule.get("source", "")
33
+ destination = rule.get("destination", "")
34
+ status = 301 if rule.get("permanent") else 302
35
+ routes.append({
36
+ "src": source,
37
+ "dest": destination,
38
+ "status": status,
39
+ })
40
+
41
+ # Add rewrites
42
+ rewrites = config.get("rewrites", {}).get("rule", [])
43
+ for rule in rewrites:
44
+ source = rule.get("source", "")
45
+ destination = rule.get("destination", "")
46
+ routes.append({
47
+ "src": source,
48
+ "dest": destination,
49
+ })
50
+
51
+ # Add headers
52
+ headers = config.get("headers", {}).get("rule", [])
53
+ for rule in headers:
54
+ source = rule.get("source", "/**")
55
+ sets = rule.get("set", {})
56
+ headers_dict = {}
57
+ for h_name, h_value in sets.items():
58
+ headers_dict[h_name] = h_value
59
+ routes.append({
60
+ "src": source,
61
+ "headers": headers_dict,
62
+ "continue": True,
63
+ })
64
+
65
+ # Static assets catch-all
66
+ routes.append({
67
+ "src": "/(.*)",
68
+ "dest": "/$1",
69
+ })
70
+
71
+ with open(routes_path, "w") as f:
72
+ json.dump({"version": 1, "routes": routes}, f, indent=2)
73
+
74
+ # Generate _worker.js for API routes
75
+ dist_path = Path(dist_dir)
76
+ functions_src = dist_path / "functions"
77
+ if functions_src.exists():
78
+ worker_code = """
79
+ export default {
80
+ async fetch(request, env, ctx) {
81
+ const url = new URL(request.url);
82
+ // Handle API routes
83
+ if (url.pathname.startsWith('/api/')) {
84
+ return new Response(JSON.stringify({ message: "API route" }), {
85
+ headers: { "Content-Type": "application/json" }
86
+ });
87
+ }
88
+ // Otherwise serve static assets from KV or R2
89
+ return env.ASSETS.fetch(request);
90
+ }
91
+ }
92
+ """
93
+ with open(worker_path, "w") as f:
94
+ f.write(worker_code)
95
+
96
+ print(f"Generated Cloudflare deployment files at {root}")
@@ -0,0 +1,91 @@
1
+ """
2
+ Netlify adapter for TWLang framework.
3
+ Generates netlify.toml and functions directory.
4
+ """
5
+
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Dict, Any
9
+
10
+ def detect() -> dict:
11
+ """Return framework metadata for Netlify detection."""
12
+ return {
13
+ "framework": "tw",
14
+ "version": "1.0",
15
+ "buildCommand": "tw build",
16
+ "publish": "dist",
17
+ "installCommand": "pip install tw-framework",
18
+ }
19
+
20
+
21
+ def generate_netlify_output(
22
+ dist_dir: str,
23
+ config: Dict[str, Any],
24
+ project_root: str,
25
+ ) -> None:
26
+ """
27
+ Generate Netlify deployment files from build output.
28
+
29
+ Args:
30
+ dist_dir: Path to the build output directory (e.g., 'dist').
31
+ config: Parsed tw.config as a dictionary.
32
+ project_root: Root directory of the project.
33
+ """
34
+ root = Path(project_root)
35
+ netlify_toml_path = root / "netlify.toml"
36
+ functions_dir = root / "netlify" / "functions"
37
+ publish_dir = "dist"
38
+
39
+ # Build netlify.toml
40
+ toml_lines = ['[build]', 'command = "tw build --prod"', f'publish = "{publish_dir}"', '']
41
+
42
+ # Add redirects from tw.config
43
+ redirects = config.get("redirects", {}).get("rule", [])
44
+ if redirects:
45
+ toml_lines.append('[[redirects]]')
46
+ for rule in redirects:
47
+ source = rule.get("source", "")
48
+ destination = rule.get("destination", "")
49
+ status = 301 if rule.get("permanent") else 302
50
+ toml_lines.append(f' from = "{source}"')
51
+ toml_lines.append(f' to = "{destination}"')
52
+ toml_lines.append(f' status = {status}')
53
+ toml_lines.append('')
54
+
55
+ # Add headers from tw.config
56
+ headers = config.get("headers", {}).get("rule", [])
57
+ if headers:
58
+ toml_lines.append('[[headers]]')
59
+ for rule in headers:
60
+ source = rule.get("source", "/**")
61
+ sets = rule.get("set", {})
62
+ for h_name, h_value in sets.items():
63
+ toml_lines.append(f' for = "{source}"')
64
+ toml_lines.append(' [headers.values]')
65
+ toml_lines.append(f' {h_name} = "{h_value}"')
66
+ toml_lines.append('')
67
+
68
+ with open(netlify_toml_path, "w") as f:
69
+ f.write("\n".join(toml_lines))
70
+
71
+ # Copy API functions if present
72
+ dist_path = Path(dist_dir)
73
+ functions_src = dist_path / "functions"
74
+ if functions_src.exists():
75
+ if functions_dir.exists():
76
+ shutil.rmtree(functions_dir)
77
+ shutil.copytree(functions_src, functions_dir)
78
+
79
+ # Auto‑generate netlify.toml for zero‑config detection
80
+ netlify_toml_path = root / "netlify.toml"
81
+ if not netlify_toml_path.exists():
82
+ toml_content = (
83
+ "[build]\n"
84
+ 'command = "tw build --prod"\n'
85
+ 'publish = "dist"\n'
86
+ )
87
+ with open(netlify_toml_path, "w") as f:
88
+ f.write(toml_content)
89
+ print(f" ✅ Auto‑generated netlify.toml at {netlify_toml_path}")
90
+
91
+ print(f"Generated Netlify deployment files at {root}")
@@ -0,0 +1,192 @@
1
+ """
2
+ Vercel adapter for TWLang framework.
3
+ Generates .vercel/output compliant with Build Output API v3.
4
+ """
5
+
6
+ import json
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Dict, Any
10
+
11
+ def detect() -> dict:
12
+ """Return framework metadata for Vercel detection."""
13
+ return {
14
+ "framework": "tw",
15
+ "version": "1.0",
16
+ "buildCommand": "tw build",
17
+ "outputDirectory": "dist",
18
+ "installCommand": "pip install tw-framework",
19
+ "devCommand": "tw dev",
20
+ }
21
+
22
+
23
+ def generate_vercel_output(
24
+ dist_dir: str,
25
+ config: Dict[str, Any],
26
+ project_root: str,
27
+ ) -> None:
28
+ """
29
+ Generate .vercel/output from the build output directory.
30
+
31
+ Args:
32
+ dist_dir: Path to the build output directory (e.g., 'dist').
33
+ config: Parsed tw.config as a dictionary.
34
+ project_root: Root directory of the project.
35
+ """
36
+ output_dir = Path(project_root) / ".vercel" / "output"
37
+ # Clean previous output
38
+ if output_dir.exists():
39
+ shutil.rmtree(output_dir)
40
+ output_dir.mkdir(parents=True, exist_ok=True)
41
+
42
+ static_dir = output_dir / "static"
43
+ static_dir.mkdir(exist_ok=True)
44
+
45
+ # Copy all files from dist to static (except functions/api)
46
+ dist_path = Path(dist_dir)
47
+ if not dist_path.exists():
48
+ raise FileNotFoundError(f"Build output directory '{dist_dir}' not found")
49
+
50
+ functions_src = dist_path / "functions"
51
+ functions_dst = output_dir / "functions"
52
+
53
+ for item in dist_path.rglob("*"):
54
+ if item.is_file():
55
+ rel = item.relative_to(dist_path)
56
+ # Skip functions directory (handled separately)
57
+ if rel.parts and rel.parts[0] == "functions":
58
+ continue
59
+ dest = static_dir / rel
60
+ dest.parent.mkdir(parents=True, exist_ok=True)
61
+ shutil.copy2(item, dest)
62
+
63
+ # Handle functions if present
64
+ if functions_src.exists():
65
+ if functions_dst.exists():
66
+ shutil.rmtree(functions_dst)
67
+ shutil.copytree(functions_src, functions_dst)
68
+
69
+ # Build config.json
70
+ vercel_config = build_vercel_config(config, dist_path)
71
+ config_path = output_dir / "config.json"
72
+ with open(config_path, "w") as f:
73
+ json.dump(vercel_config, f, indent=2)
74
+
75
+ # Auto‑generate vercel.json for zero‑config detection
76
+ vercel_json_path = Path(project_root) / "vercel.json"
77
+ if not vercel_json_path.exists():
78
+ vercel_config = {
79
+ "framework": "tw",
80
+ "buildCommand": "tw build",
81
+ "outputDirectory": "dist",
82
+ "installCommand": "pip install tw-framework",
83
+ }
84
+ with open(vercel_json_path, "w") as f:
85
+ json.dump(vercel_config, f, indent=2)
86
+ print(f" ✅ Auto‑generated vercel.json at {vercel_json_path}")
87
+
88
+ print(f"Generated .vercel/output at {output_dir}")
89
+
90
+
91
+ def build_vercel_config(tw_config: Dict[str, Any], dist_path: Path) -> Dict[str, Any]:
92
+ """
93
+ Build the Vercel config.json (version 3) from tw.config and build output.
94
+ """
95
+ routes = []
96
+ headers_list = []
97
+ rewrites_list = []
98
+ redirects_list = []
99
+
100
+ # Extract headers from tw.config
101
+ headers_cfg = tw_config.get("headers", {}).get("rule", [])
102
+ for rule in headers_cfg:
103
+ source = rule.get("source", "/**")
104
+ headers = {}
105
+ sets = rule.get("set", {})
106
+ for h_name, h_value in sets.items():
107
+ headers[h_name] = h_value
108
+ if headers:
109
+ headers_list.append({
110
+ "source": source,
111
+ "headers": [{"key": k, "value": v} for k, v in headers.items()]
112
+ })
113
+
114
+ # Extract redirects
115
+ redirects_cfg = tw_config.get("redirects", {}).get("rule", [])
116
+ for rule in redirects_cfg:
117
+ source = rule.get("source", "")
118
+ destination = rule.get("destination", "")
119
+ permanent = rule.get("permanent", False)
120
+ redirects_list.append({
121
+ "source": source,
122
+ "destination": destination,
123
+ "permanent": permanent,
124
+ })
125
+
126
+ # Extract rewrites
127
+ rewrites_cfg = tw_config.get("rewrites", {}).get("rule", [])
128
+ for rule in rewrites_cfg:
129
+ source = rule.get("source", "")
130
+ destination = rule.get("destination", "")
131
+ rewrites_list.append({
132
+ "source": source,
133
+ "destination": destination,
134
+ })
135
+
136
+ # Build routes array
137
+ # 1. Handle redirects first
138
+ for r in redirects_list:
139
+ routes.append({
140
+ "src": r["source"],
141
+ "status": 308 if r.get("permanent") else 307,
142
+ "headers": {"Location": r["destination"]},
143
+ })
144
+
145
+ # 2. Handle rewrites
146
+ for r in rewrites_list:
147
+ routes.append({
148
+ "src": r["source"],
149
+ "dest": r["destination"],
150
+ })
151
+
152
+ # 3. Apply headers
153
+ for h in headers_list:
154
+ routes.append({
155
+ "src": h["source"],
156
+ "headers": h["headers"],
157
+ "continue": True,
158
+ })
159
+
160
+ # 4. Static files and functions
161
+ has_functions = (dist_path / "functions").exists()
162
+ if has_functions:
163
+ routes.append({
164
+ "src": "/api/(.*)",
165
+ "dest": "/api/$1",
166
+ })
167
+
168
+ # 5. Static files catch-all
169
+ routes.append({
170
+ "src": "/(.*)",
171
+ "dest": "/$1",
172
+ })
173
+
174
+ config = {
175
+ "version": 3,
176
+ "routes": routes,
177
+ }
178
+
179
+ # Add images config if present
180
+ images_cfg = tw_config.get("images", {})
181
+ if images_cfg:
182
+ config["images"] = {
183
+ "sizes": images_cfg.get("sizes", [640, 750, 828, 1080, 1200, 1920, 2048, 3840]),
184
+ "domains": [],
185
+ "remotePatterns": images_cfg.get("remote_patterns", []),
186
+ "minimumCacheTTL": 60,
187
+ "formats": ["image/avif", "image/webp"],
188
+ "dangerouslyAllowSVG": False,
189
+ "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;",
190
+ }
191
+
192
+ return config