kryptorious-devflow 1.0.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.
- devflow/__init__.py +6 -0
- devflow/cli.py +140 -0
- devflow/commands/__init__.py +1 -0
- devflow/commands/audit.py +508 -0
- devflow/commands/fix.py +175 -0
- devflow/commands/init.py +620 -0
- devflow/commands/ship.py +224 -0
- devflow/utils.py +87 -0
- kryptorious_devflow-1.0.0.dist-info/METADATA +201 -0
- kryptorious_devflow-1.0.0.dist-info/RECORD +13 -0
- kryptorious_devflow-1.0.0.dist-info/WHEEL +5 -0
- kryptorious_devflow-1.0.0.dist-info/entry_points.txt +2 -0
- kryptorious_devflow-1.0.0.dist-info/top_level.txt +1 -0
devflow/commands/init.py
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
"""devflow init — Smart project scaffolding.
|
|
2
|
+
|
|
3
|
+
Creates production-ready project structures with best practices baked in.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import stat
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List
|
|
10
|
+
|
|
11
|
+
from ..utils import (
|
|
12
|
+
console, print_header, print_success, print_info, print_warning, run_cmd
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TEMPLATES = ["python", "node", "fullstack", "cli", "api", "lib"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _safe_pkg_name(name: str) -> str:
|
|
20
|
+
"""Convert project name to valid Python package name."""
|
|
21
|
+
return name.replace("-", "_").replace(" ", "_").lower()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run(name: str, template: str, path: str, docker: bool,
|
|
25
|
+
ci: bool, git_init: bool, license_type: str, description: str):
|
|
26
|
+
"""Scaffold a new project."""
|
|
27
|
+
|
|
28
|
+
project_dir = Path(path).resolve() / name
|
|
29
|
+
pkg_name = _safe_pkg_name(name)
|
|
30
|
+
|
|
31
|
+
if project_dir.exists():
|
|
32
|
+
console.print(f"[red]Error:[/red] Directory '{project_dir}' already exists.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
print_header(f"DevFlow Init — Scaffolding [bold cyan]{name}[/bold cyan] ({template})")
|
|
36
|
+
|
|
37
|
+
# Build file tree
|
|
38
|
+
files = _build_template(name, pkg_name, template, description, license_type, docker, ci)
|
|
39
|
+
|
|
40
|
+
# Create directories and files
|
|
41
|
+
dirs_created = 0
|
|
42
|
+
files_created = 0
|
|
43
|
+
|
|
44
|
+
for filepath, content in files.items():
|
|
45
|
+
full_path = project_dir / filepath
|
|
46
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
if content is not None:
|
|
48
|
+
full_path.write_text(content, encoding="utf-8")
|
|
49
|
+
files_created += 1
|
|
50
|
+
else:
|
|
51
|
+
# Just create directory
|
|
52
|
+
full_path.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
dirs_created += 1
|
|
54
|
+
|
|
55
|
+
# Make scripts executable
|
|
56
|
+
for script in [".github/workflows/ci.yml"]:
|
|
57
|
+
sp = project_dir / script
|
|
58
|
+
if sp.exists():
|
|
59
|
+
sp.chmod(sp.stat().st_mode | stat.S_IEXEC)
|
|
60
|
+
|
|
61
|
+
print_success(f"Created {dirs_created} directories, {files_created} files in {project_dir}")
|
|
62
|
+
|
|
63
|
+
# Initialize git
|
|
64
|
+
if git_init:
|
|
65
|
+
code, out, err = run_cmd(["git", "init"], cwd=str(project_dir))
|
|
66
|
+
if code == 0:
|
|
67
|
+
print_success("Initialized git repository")
|
|
68
|
+
# Create .gitignore
|
|
69
|
+
gitignore = project_dir / ".gitignore"
|
|
70
|
+
gitignore.write_text(_gitignore_content(template))
|
|
71
|
+
# Initial commit
|
|
72
|
+
run_cmd(["git", "add", "-A"], cwd=str(project_dir))
|
|
73
|
+
run_cmd(["git", "commit", "-m", f"Initial commit — DevFlow scaffold ({template})"],
|
|
74
|
+
cwd=str(project_dir))
|
|
75
|
+
print_success("Created initial commit")
|
|
76
|
+
else:
|
|
77
|
+
print_warning(f"Git init failed: {err}")
|
|
78
|
+
|
|
79
|
+
# Install dependencies if possible
|
|
80
|
+
if template in ("python", "cli", "api", "lib", "fullstack"):
|
|
81
|
+
if _has_uv():
|
|
82
|
+
code, out, err = run_cmd(["uv", "pip", "install", "-e", "."], cwd=str(project_dir))
|
|
83
|
+
else:
|
|
84
|
+
code, out, err = run_cmd([os.sys.executable, "-m", "pip", "install", "-e", "."],
|
|
85
|
+
cwd=str(project_dir))
|
|
86
|
+
if code == 0:
|
|
87
|
+
print_success("Installed project in development mode")
|
|
88
|
+
else:
|
|
89
|
+
print_info("Run 'pip install -e .' to install in dev mode")
|
|
90
|
+
|
|
91
|
+
# Show next steps
|
|
92
|
+
console.print()
|
|
93
|
+
console.print("[bold]Next steps:[/bold]")
|
|
94
|
+
console.print(f" cd {name}")
|
|
95
|
+
console.print(f" devflow audit # Check project health")
|
|
96
|
+
console.print(f" devflow fix # Auto-fix issues")
|
|
97
|
+
console.print(f" devflow ship --bump patch # Ship your first release")
|
|
98
|
+
console.print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_template(name: str, pkg_name: str, template: str, description: str,
|
|
102
|
+
license_type: str, docker: bool, ci: bool) -> Dict[str, str]:
|
|
103
|
+
"""Build the file tree for a template."""
|
|
104
|
+
|
|
105
|
+
desc = description or f"{name} — built with DevFlow"
|
|
106
|
+
|
|
107
|
+
if template in ("python", "cli", "api", "lib"):
|
|
108
|
+
return _python_template(name, pkg_name, template, desc, license_type, docker, ci)
|
|
109
|
+
elif template == "node":
|
|
110
|
+
return _node_template(name, desc, license_type, docker, ci)
|
|
111
|
+
elif template == "fullstack":
|
|
112
|
+
return _fullstack_template(name, pkg_name, desc, license_type, docker, ci)
|
|
113
|
+
else:
|
|
114
|
+
return _python_template(name, pkg_name, "python", desc, license_type, docker, ci)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _python_template(display_name: str, pkg_name: str, subtype: str, desc: str,
|
|
118
|
+
license_type: str, docker: bool, ci: bool) -> Dict[str, str]:
|
|
119
|
+
"""Python project template."""
|
|
120
|
+
name = pkg_name # backward compatibility alias within this function
|
|
121
|
+
|
|
122
|
+
files = {}
|
|
123
|
+
|
|
124
|
+
# pyproject.toml
|
|
125
|
+
deps = []
|
|
126
|
+
dev_deps = ['pytest>=7.0', 'pytest-cov>=4.0', 'black>=23.0', 'ruff>=0.1', 'mypy>=1.0']
|
|
127
|
+
if subtype == "api":
|
|
128
|
+
deps = ['fastapi>=0.100', 'uvicorn>=0.23']
|
|
129
|
+
elif subtype == "cli":
|
|
130
|
+
deps = ['click>=8.0', 'rich>=13.0']
|
|
131
|
+
elif subtype == "lib":
|
|
132
|
+
dev_deps.extend(['sphinx>=7.0', 'furo>=2024'])
|
|
133
|
+
|
|
134
|
+
deps_str = ',\n '.join(f'"{d}"' for d in deps)
|
|
135
|
+
dev_str = ',\n '.join(f'"{d}"' for d in dev_deps)
|
|
136
|
+
|
|
137
|
+
# Build dependencies section (skip if empty)
|
|
138
|
+
deps_section = ""
|
|
139
|
+
if deps:
|
|
140
|
+
deps_section = f"\ndependencies = [\n {deps_str},\n]"
|
|
141
|
+
else:
|
|
142
|
+
deps_section = "\ndependencies = []"
|
|
143
|
+
|
|
144
|
+
if subtype == "lib":
|
|
145
|
+
cli_entry = ""
|
|
146
|
+
else:
|
|
147
|
+
cli_entry = f'\n[project.scripts]\n{display_name} = "{pkg_name}.cli:main"\n'
|
|
148
|
+
|
|
149
|
+
extra_requires = ""
|
|
150
|
+
if dev_deps:
|
|
151
|
+
extra_requires = f'\n[project.optional-dependencies]\ndev = [\n {dev_str},\n]\n'
|
|
152
|
+
|
|
153
|
+
files[f"src/{pkg_name}/__init__.py"] = f'"""{desc}"""\n\n__version__ = "0.1.0"\n'
|
|
154
|
+
|
|
155
|
+
if subtype != "lib":
|
|
156
|
+
files[f"src/{pkg_name}/cli.py"] = _generate_cli_stub(pkg_name, desc)
|
|
157
|
+
|
|
158
|
+
files[f"src/{pkg_name}/core.py"] = _generate_core_stub(pkg_name, subtype)
|
|
159
|
+
|
|
160
|
+
files["pyproject.toml"] = f"""[build-system]
|
|
161
|
+
requires = ["setuptools>=68", "wheel"]
|
|
162
|
+
build-backend = "setuptools.build_meta"
|
|
163
|
+
|
|
164
|
+
[project]
|
|
165
|
+
name = "{display_name}"
|
|
166
|
+
version = "0.1.0"
|
|
167
|
+
description = "{desc}"
|
|
168
|
+
readme = "README.md"
|
|
169
|
+
license = {{text = "{license_type}"}}
|
|
170
|
+
requires-python = ">=3.9"
|
|
171
|
+
authors = [{{name = "Your Name"}}]{deps_section}{extra_requires}{cli_entry}
|
|
172
|
+
[tool.setuptools.packages.find]
|
|
173
|
+
where = ["src"]
|
|
174
|
+
|
|
175
|
+
[tool.black]
|
|
176
|
+
line-length = 100
|
|
177
|
+
target-version = ["py39"]
|
|
178
|
+
|
|
179
|
+
[tool.ruff]
|
|
180
|
+
line-length = 100
|
|
181
|
+
target-version = "py39"
|
|
182
|
+
|
|
183
|
+
[tool.ruff.lint]
|
|
184
|
+
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
|
|
185
|
+
|
|
186
|
+
[tool.mypy]
|
|
187
|
+
python_version = "3.9"
|
|
188
|
+
strict = true
|
|
189
|
+
ignore_missing_imports = true
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
# Tests
|
|
193
|
+
files["tests/__init__.py"] = ""
|
|
194
|
+
files["tests/test_core.py"] = f'''"""Tests for {name}."""\n\nfrom {name}.core import hello\n\n\ndef test_hello():\n assert hello() == "Hello from {name}, world!"\n\ndef test_hello_custom():\n assert hello("World") == "Hello from {name}, World!"\n'''
|
|
195
|
+
|
|
196
|
+
# README
|
|
197
|
+
files["README.md"] = _generate_readme(name, desc, subtype)
|
|
198
|
+
|
|
199
|
+
# CI
|
|
200
|
+
if ci:
|
|
201
|
+
files[".github/workflows/ci.yml"] = _generate_ci(name)
|
|
202
|
+
|
|
203
|
+
# Docker
|
|
204
|
+
if docker:
|
|
205
|
+
files["Dockerfile"] = _generate_dockerfile(name, subtype)
|
|
206
|
+
|
|
207
|
+
# License
|
|
208
|
+
if license_type == "MIT":
|
|
209
|
+
files["LICENSE"] = _mit_license()
|
|
210
|
+
|
|
211
|
+
return files
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _node_template(name: str, desc: str, license_type: str,
|
|
215
|
+
docker: bool, ci: bool) -> Dict[str, str]:
|
|
216
|
+
"""Node.js project template."""
|
|
217
|
+
files = {}
|
|
218
|
+
|
|
219
|
+
files["package.json"] = f'''{{
|
|
220
|
+
"name": "{name}",
|
|
221
|
+
"version": "0.1.0",
|
|
222
|
+
"description": "{desc}",
|
|
223
|
+
"main": "src/index.js",
|
|
224
|
+
"scripts": {{
|
|
225
|
+
"start": "node src/index.js",
|
|
226
|
+
"test": "jest",
|
|
227
|
+
"lint": "eslint src/",
|
|
228
|
+
"format": "prettier --write src/"
|
|
229
|
+
}},
|
|
230
|
+
"license": "{license_type}"
|
|
231
|
+
}}
|
|
232
|
+
'''
|
|
233
|
+
|
|
234
|
+
files["src/index.js"] = f'''/**
|
|
235
|
+
* {name} — {desc}
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
function hello(name = "world") {{
|
|
239
|
+
return `Hello from {name}, ${{name}}!`;
|
|
240
|
+
}}
|
|
241
|
+
|
|
242
|
+
module.exports = {{ hello }};
|
|
243
|
+
|
|
244
|
+
if (require.main === module) {{
|
|
245
|
+
console.log(hello());
|
|
246
|
+
}}
|
|
247
|
+
'''
|
|
248
|
+
|
|
249
|
+
files["tests/index.test.js"] = f'''const {{ hello }} = require("../src/index");
|
|
250
|
+
|
|
251
|
+
test("returns greeting", () => {{
|
|
252
|
+
expect(hello()).toBe("Hello from {name}, world!");
|
|
253
|
+
}});
|
|
254
|
+
|
|
255
|
+
test("custom name", () => {{
|
|
256
|
+
expect(hello("Dev")).toBe("Hello from {name}, Dev!");
|
|
257
|
+
}});
|
|
258
|
+
'''
|
|
259
|
+
|
|
260
|
+
files["README.md"] = _generate_readme(name, desc, "node")
|
|
261
|
+
files[".gitignore"] = "node_modules/\ndist/\n.env\n"
|
|
262
|
+
|
|
263
|
+
if ci:
|
|
264
|
+
files[".github/workflows/ci.yml"] = _generate_ci_node(name)
|
|
265
|
+
if docker:
|
|
266
|
+
files["Dockerfile"] = _generate_dockerfile_node(name)
|
|
267
|
+
if license_type == "MIT":
|
|
268
|
+
files["LICENSE"] = _mit_license()
|
|
269
|
+
|
|
270
|
+
return files
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _fullstack_template(name: str, desc: str, license_type: str,
|
|
274
|
+
docker: bool, ci: bool) -> Dict[str, str]:
|
|
275
|
+
"""Full-stack project (Python backend + Node frontend)."""
|
|
276
|
+
files = {}
|
|
277
|
+
|
|
278
|
+
# Backend
|
|
279
|
+
files.update(_python_template(f"{name}", "api", desc, license_type, docker, ci))
|
|
280
|
+
|
|
281
|
+
# Frontend
|
|
282
|
+
fe_name = f"{name}-frontend"
|
|
283
|
+
fe_files = _node_template(fe_name, f"{desc} — frontend", license_type, docker, ci)
|
|
284
|
+
for k, v in fe_files.items():
|
|
285
|
+
files[f"frontend/{k}"] = v
|
|
286
|
+
|
|
287
|
+
# Root docker-compose
|
|
288
|
+
if docker:
|
|
289
|
+
files["docker-compose.yml"] = f"""version: "3.8"
|
|
290
|
+
services:
|
|
291
|
+
backend:
|
|
292
|
+
build:
|
|
293
|
+
context: .
|
|
294
|
+
dockerfile: Dockerfile
|
|
295
|
+
ports:
|
|
296
|
+
- "8000:8000"
|
|
297
|
+
volumes:
|
|
298
|
+
- ./src:/app/src
|
|
299
|
+
environment:
|
|
300
|
+
- ENV=development
|
|
301
|
+
|
|
302
|
+
frontend:
|
|
303
|
+
build:
|
|
304
|
+
context: ./frontend
|
|
305
|
+
dockerfile: Dockerfile
|
|
306
|
+
ports:
|
|
307
|
+
- "3000:3000"
|
|
308
|
+
volumes:
|
|
309
|
+
- ./frontend/src:/app/src
|
|
310
|
+
depends_on:
|
|
311
|
+
- backend
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
# Root README
|
|
315
|
+
files["README.md"] = f"""# {name}
|
|
316
|
+
|
|
317
|
+
{desc}
|
|
318
|
+
|
|
319
|
+
## Architecture
|
|
320
|
+
|
|
321
|
+
- `backend/` — Python FastAPI backend
|
|
322
|
+
- `frontend/` — Node.js React frontend
|
|
323
|
+
|
|
324
|
+
## Quick Start
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Backend
|
|
328
|
+
cd backend
|
|
329
|
+
pip install -e ".[dev]"
|
|
330
|
+
uvicorn {name}.main:app --reload
|
|
331
|
+
|
|
332
|
+
# Frontend
|
|
333
|
+
cd frontend
|
|
334
|
+
npm install
|
|
335
|
+
npm start
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Built with [DevFlow](https://devflow.sh).
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
return files
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _generate_cli_stub(name: str, desc: str) -> str:
|
|
345
|
+
return f'''"""{desc} — CLI entry point."""
|
|
346
|
+
|
|
347
|
+
import click
|
|
348
|
+
from rich.console import Console
|
|
349
|
+
|
|
350
|
+
console = Console()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@click.group()
|
|
354
|
+
@click.version_option(version="0.1.0")
|
|
355
|
+
def main():
|
|
356
|
+
"""{desc}"""
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@main.command()
|
|
361
|
+
@click.argument("name", default="world")
|
|
362
|
+
def hello(name: str):
|
|
363
|
+
"""Say hello."""
|
|
364
|
+
from .core import hello as say_hello
|
|
365
|
+
console.print(say_hello(name))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
if __name__ == "__main__":
|
|
369
|
+
main()
|
|
370
|
+
'''
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _generate_core_stub(name: str, subtype: str) -> str:
|
|
374
|
+
return f'''"""{name} core module."""
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def hello(name: str = "world") -> str:
|
|
378
|
+
"""Return a friendly greeting.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
name: Who to greet (default: world)
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
A greeting string
|
|
385
|
+
"""
|
|
386
|
+
return f"Hello from {name}, {{name}}!"
|
|
387
|
+
'''
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _generate_readme(name: str, desc: str, subtype: str) -> str:
|
|
391
|
+
badges = ""
|
|
392
|
+
if subtype in ("python", "cli", "api", "lib"):
|
|
393
|
+
badges = "\n"
|
|
394
|
+
|
|
395
|
+
return f"""# {name}
|
|
396
|
+
|
|
397
|
+
{badges}
|
|
398
|
+
{desc}
|
|
399
|
+
|
|
400
|
+
## Quick Start
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Install
|
|
404
|
+
pip install -e ".[dev]"
|
|
405
|
+
|
|
406
|
+
# Run tests
|
|
407
|
+
pytest
|
|
408
|
+
|
|
409
|
+
# Format code
|
|
410
|
+
black src/ tests/
|
|
411
|
+
ruff check src/ tests/ --fix
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Project Structure
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
src/{name}/ # Application code
|
|
418
|
+
tests/ # Test suite
|
|
419
|
+
pyproject.toml # Project config and dependencies
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Built with DevFlow
|
|
423
|
+
|
|
424
|
+
Scaffolded using [DevFlow](https://devflow.sh) — the developer workflow automation CLI.
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _generate_ci(name: str) -> str:
|
|
429
|
+
return f"""name: CI
|
|
430
|
+
|
|
431
|
+
on:
|
|
432
|
+
push:
|
|
433
|
+
branches: [main, master]
|
|
434
|
+
pull_request:
|
|
435
|
+
branches: [main, master]
|
|
436
|
+
|
|
437
|
+
jobs:
|
|
438
|
+
test:
|
|
439
|
+
runs-on: ubuntu-latest
|
|
440
|
+
strategy:
|
|
441
|
+
matrix:
|
|
442
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
443
|
+
|
|
444
|
+
steps:
|
|
445
|
+
- uses: actions/checkout@v4
|
|
446
|
+
|
|
447
|
+
- name: Set up Python
|
|
448
|
+
uses: actions/setup-python@v5
|
|
449
|
+
with:
|
|
450
|
+
python-version: ${{{{ matrix.python-version }}}}
|
|
451
|
+
|
|
452
|
+
- name: Install dependencies
|
|
453
|
+
run: |
|
|
454
|
+
pip install -e ".[dev]"
|
|
455
|
+
|
|
456
|
+
- name: Lint
|
|
457
|
+
run: |
|
|
458
|
+
ruff check src/ tests/
|
|
459
|
+
black --check src/ tests/
|
|
460
|
+
|
|
461
|
+
- name: Type check
|
|
462
|
+
run: |
|
|
463
|
+
mypy src/
|
|
464
|
+
|
|
465
|
+
- name: Test
|
|
466
|
+
run: |
|
|
467
|
+
pytest --cov={name} --cov-report=term-missing
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _generate_ci_node(name: str) -> str:
|
|
472
|
+
return f"""name: CI
|
|
473
|
+
|
|
474
|
+
on:
|
|
475
|
+
push:
|
|
476
|
+
branches: [main, master]
|
|
477
|
+
pull_request:
|
|
478
|
+
branches: [main, master]
|
|
479
|
+
|
|
480
|
+
jobs:
|
|
481
|
+
test:
|
|
482
|
+
runs-on: ubuntu-latest
|
|
483
|
+
strategy:
|
|
484
|
+
matrix:
|
|
485
|
+
node-version: [18, 20, 22]
|
|
486
|
+
|
|
487
|
+
steps:
|
|
488
|
+
- uses: actions/checkout@v4
|
|
489
|
+
|
|
490
|
+
- name: Set up Node.js
|
|
491
|
+
uses: actions/setup-node@v4
|
|
492
|
+
with:
|
|
493
|
+
node-version: ${{{{ matrix.node-version }}}}
|
|
494
|
+
|
|
495
|
+
- name: Install dependencies
|
|
496
|
+
run: npm ci
|
|
497
|
+
|
|
498
|
+
- name: Lint
|
|
499
|
+
run: npm run lint
|
|
500
|
+
|
|
501
|
+
- name: Test
|
|
502
|
+
run: npm test
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _generate_dockerfile(name: str, subtype: str) -> str:
|
|
507
|
+
if subtype == "api":
|
|
508
|
+
cmd = f'CMD ["uvicorn", "{name}.main:app", "--host", "0.0.0.0", "--port", "8000"]'
|
|
509
|
+
elif subtype == "cli":
|
|
510
|
+
cmd = f'CMD ["{name}"]'
|
|
511
|
+
else:
|
|
512
|
+
cmd = f'CMD ["python", "-m", "{name}"]'
|
|
513
|
+
|
|
514
|
+
return f"""FROM python:3.12-slim
|
|
515
|
+
|
|
516
|
+
WORKDIR /app
|
|
517
|
+
|
|
518
|
+
COPY pyproject.toml .
|
|
519
|
+
COPY src/ src/
|
|
520
|
+
|
|
521
|
+
RUN pip install --no-cache-dir -e .
|
|
522
|
+
|
|
523
|
+
{cmd}
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _generate_dockerfile_node(name: str) -> str:
|
|
528
|
+
return f"""FROM node:22-alpine
|
|
529
|
+
|
|
530
|
+
WORKDIR /app
|
|
531
|
+
|
|
532
|
+
COPY package.json package-lock.json ./
|
|
533
|
+
RUN npm ci --production
|
|
534
|
+
|
|
535
|
+
COPY src/ src/
|
|
536
|
+
|
|
537
|
+
CMD ["node", "src/index.js"]
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _gitignore_content(template: str) -> str:
|
|
542
|
+
base = """# Python
|
|
543
|
+
__pycache__/
|
|
544
|
+
*.py[cod]
|
|
545
|
+
*.egg-info/
|
|
546
|
+
dist/
|
|
547
|
+
build/
|
|
548
|
+
.eggs/
|
|
549
|
+
*.egg
|
|
550
|
+
|
|
551
|
+
# Virtual environments
|
|
552
|
+
.venv/
|
|
553
|
+
venv/
|
|
554
|
+
env/
|
|
555
|
+
|
|
556
|
+
# IDE
|
|
557
|
+
.vscode/
|
|
558
|
+
.idea/
|
|
559
|
+
*.swp
|
|
560
|
+
*.swo
|
|
561
|
+
*~
|
|
562
|
+
|
|
563
|
+
# OS
|
|
564
|
+
.DS_Store
|
|
565
|
+
Thumbs.db
|
|
566
|
+
|
|
567
|
+
# Environment
|
|
568
|
+
.env
|
|
569
|
+
.env.local
|
|
570
|
+
.env.*.local
|
|
571
|
+
|
|
572
|
+
# Coverage
|
|
573
|
+
htmlcov/
|
|
574
|
+
.coverage
|
|
575
|
+
.coverage.*
|
|
576
|
+
coverage.xml
|
|
577
|
+
|
|
578
|
+
# Misc
|
|
579
|
+
*.log
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
if template == "node":
|
|
583
|
+
return "node_modules/\ndist/\n.env\n" + base.split("__pycache__")[0]
|
|
584
|
+
elif template == "fullstack":
|
|
585
|
+
return "node_modules/\ndist/\n" + base
|
|
586
|
+
|
|
587
|
+
return base
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _mit_license() -> str:
|
|
591
|
+
from datetime import datetime
|
|
592
|
+
year = datetime.now().year
|
|
593
|
+
return f"""MIT License
|
|
594
|
+
|
|
595
|
+
Copyright (c) {year}
|
|
596
|
+
|
|
597
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
598
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
599
|
+
in the Software without restriction, including without limitation the rights
|
|
600
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
601
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
602
|
+
furnished to do so, subject to the following conditions:
|
|
603
|
+
|
|
604
|
+
The above copyright notice and this permission notice shall be included in all
|
|
605
|
+
copies or substantial portions of the Software.
|
|
606
|
+
|
|
607
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
608
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
609
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
610
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
611
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
612
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
613
|
+
SOFTWARE.
|
|
614
|
+
"""
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _has_uv() -> bool:
|
|
618
|
+
"""Check if uv is available."""
|
|
619
|
+
import subprocess
|
|
620
|
+
return subprocess.run(["which", "uv"], capture_output=True).returncode == 0
|