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.
@@ -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 = "![Python](https://img.shields.io/badge/python-3.9+-blue.svg)\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