rapidkit 0.11.1 → 0.11.3
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.
- package/README.md +10 -7
- package/dist/index.js +419 -57
- package/dist/package.json +1 -1
- package/package.json +1 -1
- package/templates/kits/fastapi-standard/.rapidkit/cli.py.j2 +255 -0
- package/templates/kits/fastapi-standard/.rapidkit/project.json.j2 +6 -0
- package/templates/kits/fastapi-standard/.rapidkit/rapidkit.j2 +67 -0
- package/templates/kits/fastapi-standard/README.md.j2 +7 -6
- package/templates/kits/fastapi-standard/src/cli.py.j2 +10 -8
- package/templates/kits/fastapi-standard/src/main.py.j2 +15 -12
- package/templates/kits/fastapi-standard/src/routing/__init__.py.j2 +1 -1
- package/templates/kits/fastapi-standard/src/routing/health.py.j2 +20 -1
package/README.md
CHANGED
|
@@ -30,8 +30,8 @@ cd my-workspace
|
|
|
30
30
|
# Generate your first project
|
|
31
31
|
node generate-demo.js api-project
|
|
32
32
|
cd api-project
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
rapidkit init # Install dependencies
|
|
34
|
+
rapidkit dev # Start dev server
|
|
35
35
|
|
|
36
36
|
# Go back and create more projects
|
|
37
37
|
cd ..
|
|
@@ -74,14 +74,17 @@ my-workspace/ # Workspace (container)
|
|
|
74
74
|
├── package.json # npm configuration
|
|
75
75
|
├── README.md # Workspace instructions
|
|
76
76
|
├── api-project/ # Project 1
|
|
77
|
+
│ ├── .rapidkit/ # Local CLI (rapidkit init/dev/test)
|
|
77
78
|
│ ├── src/
|
|
78
79
|
│ ├── tests/
|
|
79
80
|
│ └── pyproject.toml
|
|
80
81
|
├── auth-service/ # Project 2
|
|
82
|
+
│ ├── .rapidkit/
|
|
81
83
|
│ ├── src/
|
|
82
84
|
│ ├── tests/
|
|
83
85
|
│ └── pyproject.toml
|
|
84
86
|
└── data-service/ # Project 3
|
|
87
|
+
├── .rapidkit/
|
|
85
88
|
├── src/
|
|
86
89
|
├── tests/
|
|
87
90
|
└── pyproject.toml
|
|
@@ -299,8 +302,8 @@ npx rapidkit my-workspace --test-mode
|
|
|
299
302
|
|
|
300
303
|
3. **Install dependencies and run**:
|
|
301
304
|
```bash
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
rapidkit init # Install dependencies
|
|
306
|
+
rapidkit dev # Start dev server
|
|
304
307
|
```
|
|
305
308
|
|
|
306
309
|
4. **Create more projects** (go back to workspace):
|
|
@@ -335,7 +338,7 @@ npx rapidkit my-workspace --test-mode
|
|
|
335
338
|
4. **Navigate and run**:
|
|
336
339
|
```bash
|
|
337
340
|
cd api-service
|
|
338
|
-
rapidkit
|
|
341
|
+
rapidkit dev
|
|
339
342
|
```
|
|
340
343
|
|
|
341
344
|
5. **Create more projects** (from workspace root):
|
|
@@ -421,7 +424,7 @@ After setting up your demo project:
|
|
|
421
424
|
## Related Projects
|
|
422
425
|
|
|
423
426
|
- **RapidKit Python** - The core framework (coming soon to PyPI)
|
|
424
|
-
- **RapidKit Marketplace** - Browse and share modules (https://rapidkit.
|
|
427
|
+
- **RapidKit Marketplace** - Browse and share modules (https://rapidkit.top)
|
|
425
428
|
- **GitHub**: https://github.com/getrapidkit
|
|
426
429
|
|
|
427
430
|
## Contributing
|
|
@@ -481,7 +484,7 @@ MIT
|
|
|
481
484
|
## Support
|
|
482
485
|
|
|
483
486
|
- 🐛 Report issues: [GitHub Issues](https://github.com/getrapidkit/rapidkit-npm/issues)
|
|
484
|
-
- 📚 Docs: https://rapidkit.
|
|
487
|
+
- 📚 Docs: https://rapidkit.top
|
|
485
488
|
- 💬 Community: Coming soon
|
|
486
489
|
|
|
487
490
|
---
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import i from'chalk';import {readFileSync,promises}from'fs';import*as
|
|
3
|
-
[${e}/${o}]`),i.white(r));}},s=new G;var te=".rapidkitrc.json";async function
|
|
2
|
+
import {Command}from'commander';import i from'chalk';import {readFileSync,promises}from'fs';import*as y from'fs-extra';import h,{dirname,join}from'path';import oe from'inquirer';import z from'ora';import {execa}from'execa';import ee from'os';import le from'nunjucks';import {fileURLToPath}from'url';import Pe from'validate-npm-package-name';var G=class{debugEnabled=false;setDebug(e){this.debugEnabled=e;}debug(e,...o){this.debugEnabled&&console.log(i.gray(`[DEBUG] ${e}`),...o);}info(e,...o){console.log(i.blue(e),...o);}success(e,...o){console.log(i.green(e),...o);}warn(e,...o){console.log(i.yellow(e),...o);}error(e,...o){console.error(i.red(e),...o);}step(e,o,r){console.log(i.cyan(`
|
|
3
|
+
[${e}/${o}]`),i.white(r));}},s=new G;var te=".rapidkitrc.json";async function W(){let t=h.join(ee.homedir(),te);try{let e=await promises.readFile(t,"utf-8"),o=JSON.parse(e);return s.debug(`Loaded config from ${t}`),o}catch{return s.debug("No user config found, using defaults"),{}}}function F(t){return process.env.RAPIDKIT_DEV_PATH||t.testRapidKitPath||void 0}var u=class extends Error{constructor(o,r,a){super(o);this.code=r;this.details=a;this.name="RapidKitError",Error.captureStackTrace(this,this.constructor);}},P=class extends u{constructor(e,o){let r=o?`Python ${e}+ required, found ${o}`:`Python ${e}+ not found`;super(r,"PYTHON_NOT_FOUND","Please install Python from https://www.python.org/downloads/");}},T=class extends u{constructor(){super("Poetry is not installed","POETRY_NOT_FOUND","Install Poetry from https://python-poetry.org/docs/#installation");}},N=class extends u{constructor(){super("pipx is not installed","PIPX_NOT_FOUND","Install pipx from https://pypa.github.io/pipx/installation/");}},M=class extends u{constructor(e){super(`Directory "${e}" already exists`,"DIRECTORY_EXISTS","Please choose a different name or remove the existing directory");}},k=class extends u{constructor(e,o){super(`Invalid project name: "${e}"`,"INVALID_PROJECT_NAME",o);}},j=class extends u{constructor(e,o){let r=`Installation failed at: ${e}`,a=`${o.message}
|
|
4
4
|
|
|
5
5
|
Troubleshooting:
|
|
6
6
|
- Check your internet connection
|
|
7
7
|
- Verify Python/Poetry installation
|
|
8
|
-
- Try running with --debug flag for more details`;super(r,"INSTALLATION_ERROR",
|
|
8
|
+
- Try running with --debug flag for more details`;super(r,"INSTALLATION_ERROR",a);}},I=class extends u{constructor(){super("RapidKit Python package is not yet available on PyPI","RAPIDKIT_NOT_AVAILABLE",`Available options:
|
|
9
9
|
1. Demo mode: npx rapidkit my-workspace --demo
|
|
10
10
|
2. Test mode: npx rapidkit my-workspace --test-mode (requires local RapidKit)
|
|
11
|
-
3. Wait for official PyPI release (coming soon)`);}};async function U(t,e){let{skipGit:o=false,testMode:r=false,demoMode:
|
|
11
|
+
3. Wait for official PyPI release (coming soon)`);}};async function U(t,e){let{skipGit:o=false,testMode:r=false,demoMode:a=false,dryRun:m=false,userConfig:d={}}=e,n=t,c=h.resolve(process.cwd(),n);if(await y.pathExists(c))throw new M(n);if(m){await ce(c,n,a,d);return}if(a){await se(c,n,o);return}let f=await oe.prompt([{type:"list",name:"pythonVersion",message:"Select Python version for RapidKit:",choices:["3.10","3.11","3.12"],default:d.pythonVersion||"3.11"},{type:"list",name:"installMethod",message:"How would you like to install RapidKit?",choices:[{name:"\u{1F3AF} Poetry (Recommended - includes virtual env)",value:"poetry"},{name:"\u{1F4E6} pip with venv (Standard)",value:"venv"},{name:"\u{1F527} pipx (Global isolated install)",value:"pipx"}],default:d.defaultInstallMethod||"poetry"}]);s.step(1,3,"Setting up RapidKit environment");let g=z("Creating directory").start();try{if(await y.ensureDir(c),g.succeed("Directory created"),f.installMethod==="poetry"?await ie(c,f.pythonVersion,g,r,d):f.installMethod==="venv"?await re(c,f.pythonVersion,g,r,d):await ae(c,g,r,d),await ne(c,f.installMethod),g.succeed("RapidKit environment ready!"),!e.skipGit){g.start("Initializing git repository");try{await execa("git",["init"],{cwd:c}),await y.outputFile(h.join(c,".gitignore"),`.venv/
|
|
12
12
|
__pycache__/
|
|
13
13
|
*.pyc
|
|
14
14
|
.env
|
|
15
15
|
.rapidkit-workspace/
|
|
16
|
-
`,"utf-8"),await execa("git",["add","."],{cwd:
|
|
16
|
+
`,"utf-8"),await execa("git",["add","."],{cwd:c}),await execa("git",["commit","-m","Initial commit: RapidKit environment"],{cwd:c}),g.succeed("Git repository initialized");}catch{g.warn("Could not initialize git repository");}}if(console.log(i.green(`
|
|
17
17
|
\u2728 RapidKit environment created successfully!
|
|
18
|
-
`)),console.log(i.cyan("\u{1F4C2} Location:"),i.white(
|
|
19
|
-
`)),console.log(i.white(` cd ${
|
|
18
|
+
`)),console.log(i.cyan("\u{1F4C2} Location:"),i.white(c)),console.log(i.cyan(`\u{1F680} Get started:
|
|
19
|
+
`)),console.log(i.white(` cd ${n}`)),f.installMethod==="poetry"){let v="source $(poetry env info --path)/bin/activate";try{let{stdout:K}=await execa("poetry",["--version"]),b=K.match(/Poetry.*?(\d+)\.(\d+)/);b&&(parseInt(b[1])>=2?v="source $(poetry env info --path)/bin/activate":v="poetry shell");}catch{}console.log(i.white(` ${v} # Or: poetry run rapidkit`)),console.log(i.white(" rapidkit create # Interactive mode")),console.log(i.white(" cd <project-name> && rapidkit init && rapidkit dev"));}else f.installMethod==="venv"?(console.log(i.white(" source .venv/bin/activate # On Windows: .venv\\Scripts\\activate")),console.log(i.white(" rapidkit create # Interactive mode")),console.log(i.white(" cd <project-name> && rapidkit init && rapidkit dev"))):(console.log(i.white(" rapidkit create # Interactive mode")),console.log(i.white(" cd <project-name> && rapidkit init && rapidkit dev")));console.log(i.white(`
|
|
20
20
|
\u{1F4A1} For more information, check the README.md file.`)),console.log(i.cyan(`
|
|
21
|
-
\u{1F4DA} RapidKit commands:`)),console.log(i.white(" rapidkit create - Create a new project (interactive)")),console.log(i.white(" rapidkit
|
|
22
|
-
`));}catch(v){
|
|
23
|
-
\u274C Error:`),v);try{await
|
|
24
|
-
package-mode = false`);if(await promises.writeFile(m,
|
|
25
|
-
`,"utf-8");}async function
|
|
21
|
+
\u{1F4DA} RapidKit commands:`)),console.log(i.white(" rapidkit create - Create a new project (interactive)")),console.log(i.white(" rapidkit dev - Run development server")),console.log(i.white(" rapidkit add module <name> - Add a module (e.g., settings)")),console.log(i.white(" rapidkit list - List available kits")),console.log(i.white(" rapidkit modules - List available modules")),console.log(i.white(` rapidkit --help - Show all commands
|
|
22
|
+
`));}catch(v){g.fail("Failed to create RapidKit environment"),console.error(i.red(`
|
|
23
|
+
\u274C Error:`),v);try{await y.remove(c);}catch{}process.exit(1);}}async function ie(t,e,o,r,a){o.start("Checking Poetry installation");try{await execa("poetry",["--version"]),o.succeed("Poetry found");}catch{throw new T}o.start("Initializing Poetry project"),await execa("poetry",["init","--no-interaction","--python",`^${e}`],{cwd:t});let m=h.join(t,"pyproject.toml"),n=(await promises.readFile(m,"utf-8")).replace("[tool.poetry]",`[tool.poetry]
|
|
24
|
+
package-mode = false`);if(await promises.writeFile(m,n,"utf-8"),o.succeed("Poetry project initialized"),o.start("Installing RapidKit"),r){let c=F(a||{});if(!c)throw new j("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));s.debug(`Installing from local path: ${c}`),o.text="Installing RapidKit from local path (test mode)",await execa("poetry",["add",c],{cwd:t});}else {o.text="Installing RapidKit from PyPI";try{await execa("poetry",["add","rapidkit"],{cwd:t});}catch{throw new I}}o.succeed("RapidKit installed");}async function re(t,e,o,r,a){o.start(`Checking Python ${e}`);let m="python3";try{let{stdout:n}=await execa(m,["--version"]),c=n.match(/Python (\d+\.\d+)/)?.[1];if(c&&parseFloat(c)<parseFloat(e))throw new P(e,c);o.succeed(`Python ${c} found`);}catch(n){throw n instanceof P?n:new P(e)}o.start("Creating virtual environment"),await execa(m,["-m","venv",".venv"],{cwd:t}),o.succeed("Virtual environment created"),o.start("Installing RapidKit");let d=h.join(t,".venv","bin","pip");if(await execa(d,["install","--upgrade","pip"],{cwd:t}),r){let n=F(a||{});if(!n)throw new j("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));s.debug(`Installing from local path: ${n}`),o.text="Installing RapidKit from local path (test mode)",await execa(d,["install","-e",n],{cwd:t});}else {o.text="Installing RapidKit from PyPI";try{await execa(d,["install","rapidkit"],{cwd:t});}catch{throw new I}}o.succeed("RapidKit installed");}async function ae(t,e,o,r){e.start("Checking pipx installation");try{await execa("pipx",["--version"]),e.succeed("pipx found");}catch{throw new N}if(e.start("Installing RapidKit globally with pipx"),o){let a=F(r||{});if(!a)throw new j("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));s.debug(`Installing from local path: ${a}`),e.text="Installing RapidKit from local path (test mode)",await execa("pipx",["install","-e",a]);}else {e.text="Installing RapidKit from PyPI";try{await execa("pipx",["install","rapidkit"]);}catch{throw new I}}e.succeed("RapidKit installed globally"),await y.outputFile(h.join(t,".rapidkit-global"),`RapidKit installed globally with pipx
|
|
25
|
+
`,"utf-8");}async function ne(t,e){let r=`# RapidKit Workspace
|
|
26
26
|
|
|
27
27
|
This directory contains a RapidKit development environment.
|
|
28
28
|
|
|
@@ -59,14 +59,14 @@ Interactive mode will guide you through selecting a kit and configuring your pro
|
|
|
59
59
|
|
|
60
60
|
\`\`\`bash
|
|
61
61
|
cd my-project
|
|
62
|
-
# Install dependencies:
|
|
63
|
-
|
|
62
|
+
# Install dependencies (preferred):
|
|
63
|
+
rapidkit init
|
|
64
64
|
|
|
65
|
-
# Run the server (
|
|
66
|
-
rapidkit
|
|
65
|
+
# Run the server (project-aware):
|
|
66
|
+
rapidkit dev
|
|
67
67
|
|
|
68
|
-
# Or with poetry run:
|
|
69
|
-
poetry run rapidkit
|
|
68
|
+
# Or with poetry run (manual / advanced):
|
|
69
|
+
poetry run rapidkit dev
|
|
70
70
|
|
|
71
71
|
# Or manually:
|
|
72
72
|
uvicorn src.main:app --reload
|
|
@@ -88,7 +88,7 @@ rapidkit modules list
|
|
|
88
88
|
|
|
89
89
|
- \`rapidkit create\` - Create a new project (interactive)
|
|
90
90
|
- \`rapidkit create project <kit> <name>\` - Create project with specific kit
|
|
91
|
-
- \`rapidkit
|
|
91
|
+
- \`rapidkit dev\` - Run development server
|
|
92
92
|
- \`rapidkit add module <name>\` - Add a module (e.g., \`rapidkit add module settings\`)
|
|
93
93
|
- \`rapidkit list\` - List available kits
|
|
94
94
|
- \`rapidkit modules\` - List available modules
|
|
@@ -98,7 +98,7 @@ rapidkit modules list
|
|
|
98
98
|
|
|
99
99
|
## RapidKit Documentation
|
|
100
100
|
|
|
101
|
-
For full documentation, visit: [RapidKit Docs](https://rapidkit.
|
|
101
|
+
For full documentation, visit: [RapidKit Docs](https://rapidkit.top) *(or appropriate URL)*
|
|
102
102
|
|
|
103
103
|
## Workspace Structure
|
|
104
104
|
|
|
@@ -117,7 +117,7 @@ If you encounter issues:
|
|
|
117
117
|
2. Check RapidKit installation: \`rapidkit --version\`
|
|
118
118
|
3. Run diagnostics: \`rapidkit doctor\`
|
|
119
119
|
4. Visit RapidKit documentation or GitHub issues
|
|
120
|
-
`;await promises.writeFile(
|
|
120
|
+
`;await promises.writeFile(h.join(t,"README.md"),r,"utf-8");}async function se(t,e,o){let r=z("Creating demo workspace").start();try{await y.ensureDir(t),r.succeed("Directory created"),r.start("Setting up demo kit generator");let a=JSON.stringify({name:`${e}-workspace`,version:"1.0.0",private:!0,description:"RapidKit demo workspace",scripts:{generate:"node generate-demo.js"}},null,2);await promises.writeFile(h.join(t,"package.json"),a,"utf-8"),await promises.writeFile(h.join(t,"generate-demo.js"),`#!/usr/bin/env node
|
|
121
121
|
/**
|
|
122
122
|
* Demo Kit Generator - Create FastAPI demo projects
|
|
123
123
|
*
|
|
@@ -132,8 +132,9 @@ If you encounter issues:
|
|
|
132
132
|
* npm run generate my-api
|
|
133
133
|
*/
|
|
134
134
|
|
|
135
|
-
const
|
|
135
|
+
const fs = require('fs');
|
|
136
136
|
const path = require('path');
|
|
137
|
+
const readline = require('readline');
|
|
137
138
|
|
|
138
139
|
const projectName = process.argv[2];
|
|
139
140
|
|
|
@@ -144,21 +145,375 @@ if (!projectName) {
|
|
|
144
145
|
process.exit(1);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
const rl = readline.createInterface({
|
|
149
|
+
input: process.stdin,
|
|
150
|
+
output: process.stdout
|
|
151
|
+
});
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
function ask(question, defaultValue) {
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
rl.question(\`\${question} (\${defaultValue}): \`, (answer) => {
|
|
156
|
+
resolve(answer || defaultValue);
|
|
157
|
+
});
|
|
155
158
|
});
|
|
156
|
-
console.log(\`\\n\u2705 Demo project created at: \${targetPath}\\n\`);
|
|
157
|
-
} catch (_error) {
|
|
158
|
-
console.error('\\n\u274C Failed to generate demo project\\n');
|
|
159
|
-
process.exit(1);
|
|
160
159
|
}
|
|
161
|
-
|
|
160
|
+
|
|
161
|
+
async function main() {
|
|
162
|
+
const targetPath = path.join(process.cwd(), projectName);
|
|
163
|
+
|
|
164
|
+
if (fs.existsSync(targetPath)) {
|
|
165
|
+
console.error(\`\\n\u274C Directory "\${projectName}" already exists\\n\`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(\`\\n\u{1F680} Creating FastAPI project: \${projectName}\\n\`);
|
|
170
|
+
|
|
171
|
+
const snakeName = projectName.replace(/-/g, '_').toLowerCase();
|
|
172
|
+
const project_name = await ask('Project name (snake_case)', snakeName);
|
|
173
|
+
const author = await ask('Author name', process.env.USER || 'RapidKit User');
|
|
174
|
+
const description = await ask('Description', 'FastAPI service generated with RapidKit');
|
|
175
|
+
|
|
176
|
+
rl.close();
|
|
177
|
+
|
|
178
|
+
// Create project structure
|
|
179
|
+
const dirs = [
|
|
180
|
+
'',
|
|
181
|
+
'src',
|
|
182
|
+
'src/routing',
|
|
183
|
+
'src/modules',
|
|
184
|
+
'tests',
|
|
185
|
+
'.rapidkit'
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
for (const dir of dirs) {
|
|
189
|
+
fs.mkdirSync(path.join(targetPath, dir), { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Template files with content
|
|
193
|
+
const files = {
|
|
194
|
+
'src/__init__.py': '"""' + project_name + ' package."""\\n',
|
|
195
|
+
'src/modules/__init__.py': '"""Modules package."""\\n',
|
|
196
|
+
'tests/__init__.py': '"""Tests package."""\\n',
|
|
197
|
+
'src/main.py': \`"""\${project_name} application entrypoint."""
|
|
198
|
+
|
|
199
|
+
from __future__ import annotations
|
|
200
|
+
|
|
201
|
+
from contextlib import asynccontextmanager
|
|
202
|
+
from typing import AsyncIterator
|
|
203
|
+
|
|
204
|
+
from fastapi import FastAPI
|
|
205
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
206
|
+
|
|
207
|
+
from .routing import api_router
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@asynccontextmanager
|
|
211
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
212
|
+
"""Application lifespan context manager for startup/shutdown events."""
|
|
213
|
+
yield
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
app = FastAPI(
|
|
217
|
+
title="\${project_name}",
|
|
218
|
+
description="\${description}",
|
|
219
|
+
version="0.1.0",
|
|
220
|
+
docs_url="/docs",
|
|
221
|
+
redoc_url="/redoc",
|
|
222
|
+
lifespan=lifespan,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
app.add_middleware(
|
|
226
|
+
CORSMiddleware,
|
|
227
|
+
allow_origins=["*"],
|
|
228
|
+
allow_credentials=True,
|
|
229
|
+
allow_methods=["*"],
|
|
230
|
+
allow_headers=["*"],
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
app.include_router(api_router, prefix="/api")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
import uvicorn
|
|
238
|
+
uvicorn.run("src.main:app", host="0.0.0.0", port=8001, reload=True)
|
|
239
|
+
\`,
|
|
240
|
+
'src/routing/__init__.py': \`"""API routing configuration."""
|
|
241
|
+
|
|
242
|
+
from fastapi import APIRouter
|
|
243
|
+
|
|
244
|
+
from .health import router as health_router
|
|
245
|
+
|
|
246
|
+
api_router = APIRouter()
|
|
247
|
+
|
|
248
|
+
api_router.include_router(health_router)
|
|
249
|
+
\`,
|
|
250
|
+
'src/routing/health.py': \`"""Health check endpoints."""
|
|
251
|
+
|
|
252
|
+
from __future__ import annotations
|
|
253
|
+
|
|
254
|
+
from fastapi import APIRouter
|
|
255
|
+
|
|
256
|
+
router = APIRouter(prefix="/health", tags=["health"])
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@router.get("/", summary="Health check")
|
|
260
|
+
async def heartbeat() -> dict[str, str]:
|
|
261
|
+
"""Return basic service heartbeat."""
|
|
262
|
+
return {"status": "ok"}
|
|
263
|
+
\`,
|
|
264
|
+
'src/cli.py': \`"""CLI commands for \${project_name}."""
|
|
265
|
+
|
|
266
|
+
import subprocess
|
|
267
|
+
import sys
|
|
268
|
+
from pathlib import Path
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def dev():
|
|
272
|
+
"""Start development server with hot reload."""
|
|
273
|
+
print("\u{1F680} Starting development server...")
|
|
274
|
+
subprocess.run([
|
|
275
|
+
sys.executable, "-m", "uvicorn",
|
|
276
|
+
"src.main:app", "--reload",
|
|
277
|
+
"--host", "0.0.0.0", "--port", "8000"
|
|
278
|
+
])
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def start():
|
|
282
|
+
"""Start production server."""
|
|
283
|
+
print("\u26A1 Starting production server...")
|
|
284
|
+
subprocess.run([
|
|
285
|
+
sys.executable, "-m", "uvicorn",
|
|
286
|
+
"src.main:app",
|
|
287
|
+
"--host", "0.0.0.0", "--port", "8000"
|
|
288
|
+
])
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test():
|
|
292
|
+
"""Run tests."""
|
|
293
|
+
print("\u{1F9EA} Running tests...")
|
|
294
|
+
subprocess.run([sys.executable, "-m", "pytest", "-q"])
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
if len(sys.argv) < 2:
|
|
299
|
+
print("Usage: python -m src.cli <command>")
|
|
300
|
+
print("Commands: dev, start, test")
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
|
|
303
|
+
cmd = sys.argv[1]
|
|
304
|
+
if cmd == "dev":
|
|
305
|
+
dev()
|
|
306
|
+
elif cmd == "start":
|
|
307
|
+
start()
|
|
308
|
+
elif cmd == "test":
|
|
309
|
+
test()
|
|
310
|
+
else:
|
|
311
|
+
print(f"Unknown command: {cmd}")
|
|
312
|
+
sys.exit(1)
|
|
313
|
+
\`,
|
|
314
|
+
'pyproject.toml': \`[tool.poetry]
|
|
315
|
+
name = "\${project_name}"
|
|
316
|
+
version = "0.1.0"
|
|
317
|
+
description = "\${description}"
|
|
318
|
+
authors = ["\${author}"]
|
|
319
|
+
license = "MIT"
|
|
320
|
+
readme = "README.md"
|
|
321
|
+
package-mode = false
|
|
322
|
+
|
|
323
|
+
[tool.poetry.dependencies]
|
|
324
|
+
python = "^3.11"
|
|
325
|
+
fastapi = "^0.115.0"
|
|
326
|
+
uvicorn = {extras = ["standard"], version = "^0.32.0"}
|
|
327
|
+
pydantic = "^2.0"
|
|
328
|
+
pydantic-settings = "^2.0"
|
|
329
|
+
|
|
330
|
+
[tool.poetry.group.dev.dependencies]
|
|
331
|
+
pytest = "^8.0"
|
|
332
|
+
pytest-asyncio = "^0.24.0"
|
|
333
|
+
pytest-cov = "^6.0"
|
|
334
|
+
httpx = "^0.27"
|
|
335
|
+
black = "^24.0"
|
|
336
|
+
ruff = "^0.8"
|
|
337
|
+
mypy = "^1.0"
|
|
338
|
+
|
|
339
|
+
[tool.poetry.scripts]
|
|
340
|
+
dev = "src.cli:dev"
|
|
341
|
+
start = "src.cli:start"
|
|
342
|
+
test = "src.cli:test"
|
|
343
|
+
|
|
344
|
+
[build-system]
|
|
345
|
+
requires = ["poetry-core"]
|
|
346
|
+
build-backend = "poetry.core.masonry.api"
|
|
347
|
+
|
|
348
|
+
[tool.pytest.ini_options]
|
|
349
|
+
asyncio_mode = "auto"
|
|
350
|
+
testpaths = ["tests"]
|
|
351
|
+
|
|
352
|
+
[tool.ruff]
|
|
353
|
+
line-length = 100
|
|
354
|
+
target-version = "py311"
|
|
355
|
+
|
|
356
|
+
[tool.black]
|
|
357
|
+
line-length = 100
|
|
358
|
+
target-version = ["py311"]
|
|
359
|
+
\`,
|
|
360
|
+
'README.md': \`# \${project_name}
|
|
361
|
+
|
|
362
|
+
\${description}
|
|
363
|
+
|
|
364
|
+
## Quick start
|
|
365
|
+
|
|
366
|
+
\\\`\\\`\\\`bash
|
|
367
|
+
rapidkit init # Install dependencies
|
|
368
|
+
rapidkit dev # Start dev server
|
|
369
|
+
\\\`\\\`\\\`
|
|
370
|
+
|
|
371
|
+
## Available commands
|
|
372
|
+
|
|
373
|
+
\\\`\\\`\\\`bash
|
|
374
|
+
rapidkit init # \u{1F527} Install dependencies
|
|
375
|
+
rapidkit dev # \u{1F680} Start development server with hot reload
|
|
376
|
+
rapidkit start # \u26A1 Start production server
|
|
377
|
+
rapidkit test # \u{1F9EA} Run tests
|
|
378
|
+
rapidkit help # \u{1F4DA} Show available commands
|
|
379
|
+
\\\`\\\`\\\`
|
|
380
|
+
|
|
381
|
+
## Project layout
|
|
382
|
+
|
|
383
|
+
\\\`\\\`\\\`
|
|
384
|
+
\${project_name}/
|
|
385
|
+
\u251C\u2500\u2500 src/
|
|
386
|
+
\u2502 \u251C\u2500\u2500 main.py # FastAPI application
|
|
387
|
+
\u2502 \u251C\u2500\u2500 cli.py # CLI commands
|
|
388
|
+
\u2502 \u251C\u2500\u2500 routing/ # API routes
|
|
389
|
+
\u2502 \u2514\u2500\u2500 modules/ # Module system
|
|
390
|
+
\u251C\u2500\u2500 tests/ # Test suite
|
|
391
|
+
\u251C\u2500\u2500 pyproject.toml # Poetry configuration
|
|
392
|
+
\u2514\u2500\u2500 README.md
|
|
393
|
+
\\\`\\\`\\\`
|
|
394
|
+
\`,
|
|
395
|
+
'.rapidkit/project.json': JSON.stringify({
|
|
396
|
+
kit_name: "fastapi.standard",
|
|
397
|
+
profile: "fastapi/standard",
|
|
398
|
+
created_at: new Date().toISOString(),
|
|
399
|
+
rapidkit_version: "npm-demo"
|
|
400
|
+
}, null, 2),
|
|
401
|
+
'.rapidkit/cli.py': \`#!/usr/bin/env python3
|
|
402
|
+
"""RapidKit CLI wrapper for demo projects."""
|
|
403
|
+
|
|
404
|
+
import subprocess
|
|
405
|
+
import sys
|
|
406
|
+
from pathlib import Path
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def dev(port=8000, host="0.0.0.0"):
|
|
410
|
+
"""Start development server."""
|
|
411
|
+
print("\u{1F680} Starting development server with hot reload...")
|
|
412
|
+
subprocess.run([
|
|
413
|
+
sys.executable, "-m", "uvicorn",
|
|
414
|
+
"src.main:app", "--reload",
|
|
415
|
+
"--host", host, "--port", str(port)
|
|
416
|
+
])
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def start(port=8000, host="0.0.0.0"):
|
|
420
|
+
"""Start production server."""
|
|
421
|
+
print("\u26A1 Starting production server...")
|
|
422
|
+
subprocess.run([
|
|
423
|
+
sys.executable, "-m", "uvicorn",
|
|
424
|
+
"src.main:app",
|
|
425
|
+
"--host", host, "--port", str(port)
|
|
426
|
+
])
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def init():
|
|
430
|
+
"""Install dependencies."""
|
|
431
|
+
print("\u{1F4E6} Installing dependencies...")
|
|
432
|
+
subprocess.run(["poetry", "install"])
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test():
|
|
436
|
+
"""Run tests."""
|
|
437
|
+
print("\u{1F9EA} Running tests...")
|
|
438
|
+
subprocess.run([sys.executable, "-m", "pytest", "-q"])
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def help_cmd():
|
|
442
|
+
"""Show help."""
|
|
443
|
+
print("\u{1F4DA} Available commands:")
|
|
444
|
+
print(" init - Install dependencies")
|
|
445
|
+
print(" dev - Start dev server")
|
|
446
|
+
print(" start - Start production server")
|
|
447
|
+
print(" test - Run tests")
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
if __name__ == "__main__":
|
|
451
|
+
cmd = sys.argv[1] if len(sys.argv) > 1 else "help"
|
|
452
|
+
cmds = {"dev": dev, "start": start, "init": init, "test": test, "help": help_cmd}
|
|
453
|
+
cmds.get(cmd, help_cmd)()
|
|
454
|
+
\`,
|
|
455
|
+
'.rapidkit/rapidkit': '#!/usr/bin/env bash\\n# Local RapidKit launcher for demo projects\\nset -euo pipefail\\nSCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"\\nROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"\\ncd "$ROOT_DIR"\\n\\nif [ -f "pyproject.toml" ]; then\\n if command -v poetry >/dev/null 2>&1; then\\n exec poetry run python "$SCRIPT_DIR/cli.py" "$@"\\n fi\\nfi\\n\\necho "Poetry not found. Install with: pip install poetry"\\nexit 1\\n',
|
|
456
|
+
'.gitignore': \`# Python
|
|
457
|
+
__pycache__/
|
|
458
|
+
*.py[cod]
|
|
459
|
+
*.so
|
|
460
|
+
.Python
|
|
461
|
+
build/
|
|
462
|
+
dist/
|
|
463
|
+
*.egg-info/
|
|
464
|
+
|
|
465
|
+
# Virtual environments
|
|
466
|
+
.venv/
|
|
467
|
+
venv/
|
|
468
|
+
|
|
469
|
+
# IDEs
|
|
470
|
+
.vscode/
|
|
471
|
+
.idea/
|
|
472
|
+
|
|
473
|
+
# OS
|
|
474
|
+
.DS_Store
|
|
475
|
+
|
|
476
|
+
# Project
|
|
477
|
+
.env
|
|
478
|
+
.env.local
|
|
479
|
+
\`
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
483
|
+
fs.writeFileSync(path.join(targetPath, filePath), content);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Make scripts executable
|
|
487
|
+
try {
|
|
488
|
+
fs.chmodSync(path.join(targetPath, '.rapidkit/cli.py'), 0o755);
|
|
489
|
+
fs.chmodSync(path.join(targetPath, '.rapidkit/rapidkit'), 0o755);
|
|
490
|
+
} catch (e) {
|
|
491
|
+
// Ignore on Windows
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log(\`
|
|
495
|
+
\u2728 Demo project created successfully!
|
|
496
|
+
|
|
497
|
+
\u{1F4C2} Project: \${targetPath}
|
|
498
|
+
|
|
499
|
+
\u{1F680} Get started:
|
|
500
|
+
cd \${projectName}
|
|
501
|
+
rapidkit init # Install dependencies
|
|
502
|
+
rapidkit dev # Start dev server
|
|
503
|
+
|
|
504
|
+
\u{1F4DA} Available commands:
|
|
505
|
+
rapidkit init # \u{1F527} Install dependencies
|
|
506
|
+
rapidkit dev # \u{1F680} Start dev server with hot reload
|
|
507
|
+
rapidkit start # \u26A1 Start production server
|
|
508
|
+
rapidkit test # \u{1F9EA} Run tests
|
|
509
|
+
rapidkit help # \u{1F4DA} Show help
|
|
510
|
+
|
|
511
|
+
\u{1F4A1} For full RapidKit features: pipx install rapidkit
|
|
512
|
+
\`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
main().catch(console.error);
|
|
516
|
+
`,"utf-8");try{await execa("chmod",["+x",h.join(t,"generate-demo.js")]);}catch{}let d=`# RapidKit Demo Workspace
|
|
162
517
|
|
|
163
518
|
Welcome to your RapidKit demo workspace! This environment lets you generate FastAPI demo projects using bundled RapidKit templates, without needing to install Python RapidKit.
|
|
164
519
|
|
|
@@ -174,10 +529,10 @@ node generate-demo.js my-api
|
|
|
174
529
|
cd my-api
|
|
175
530
|
|
|
176
531
|
# Install dependencies:
|
|
177
|
-
|
|
532
|
+
rapidkit init
|
|
178
533
|
|
|
179
534
|
# Run the development server:
|
|
180
|
-
|
|
535
|
+
rapidkit dev
|
|
181
536
|
\`\`\`
|
|
182
537
|
|
|
183
538
|
Your API will be available at \`http://localhost:8000\`
|
|
@@ -210,7 +565,7 @@ Each generated demo project contains:
|
|
|
210
565
|
1. **Explore the Generated Code** - Check out \`src/main.py\` and \`src/routing/\`
|
|
211
566
|
2. **Add Routes** - Create new endpoints in \`src/routing/\`
|
|
212
567
|
3. **Install Full RapidKit** - For advanced features: \`pipx install rapidkit\`
|
|
213
|
-
4. **Read the Documentation** - Visit [RapidKit Docs](https://rapidkit.
|
|
568
|
+
4. **Read the Documentation** - Visit [RapidKit Docs](https://rapidkit.top)
|
|
214
569
|
|
|
215
570
|
## \u26A0\uFE0F Demo Mode Limitations
|
|
216
571
|
|
|
@@ -245,7 +600,7 @@ ${e}/
|
|
|
245
600
|
---
|
|
246
601
|
|
|
247
602
|
**Generated with RapidKit** | [GitHub](https://github.com/getrapidkit/rapidkit-npm)
|
|
248
|
-
`;if(await promises.writeFile(
|
|
603
|
+
`;if(await promises.writeFile(h.join(t,"README.md"),d,"utf-8"),r.succeed("Demo workspace setup complete"),!o){r.start("Initializing git repository");try{await execa("git",["init"],{cwd:t}),await y.outputFile(h.join(t,".gitignore"),`# Dependencies
|
|
249
604
|
node_modules/
|
|
250
605
|
|
|
251
606
|
# Generated projects
|
|
@@ -261,7 +616,7 @@ __pycache__/
|
|
|
261
616
|
`,"utf-8"),await execa("git",["add","."],{cwd:t}),await execa("git",["commit","-m","Initial commit: Demo workspace"],{cwd:t}),r.succeed("Git repository initialized");}catch{r.warn("Could not initialize git repository");}}console.log(i.green(`
|
|
262
617
|
\u2728 Demo workspace created successfully!
|
|
263
618
|
`)),console.log(i.cyan("\u{1F4C2} Location:"),i.white(t)),console.log(i.cyan(`\u{1F680} Get started:
|
|
264
|
-
`)),console.log(i.white(` cd ${e}`)),console.log(i.white(" node generate-demo.js my-api")),console.log(i.white(" cd my-api")),console.log(i.white("
|
|
619
|
+
`)),console.log(i.white(` cd ${e}`)),console.log(i.white(" node generate-demo.js my-api")),console.log(i.white(" cd my-api")),console.log(i.white(" rapidkit init")),console.log(i.white(" rapidkit dev")),console.log(),console.log(i.yellow("\u{1F4A1} Note:"),"This is a demo workspace. For full RapidKit features:"),console.log(i.cyan(" pipx install rapidkit")),console.log();}catch(a){throw r.fail("Failed to create demo workspace"),a}}async function ce(t,e,o,r){console.log(i.cyan(`
|
|
265
620
|
\u{1F50D} Dry-run mode - showing what would be created:
|
|
266
621
|
`)),console.log(i.white("\u{1F4C2} Project path:"),t),console.log(i.white("\u{1F4E6} Project type:"),o?"Demo workspace":"Full RapidKit environment"),o?(console.log(i.white(`
|
|
267
622
|
\u{1F4DD} Files to create:`)),console.log(i.gray(" - package.json")),console.log(i.gray(" - generate-demo.js (project generator)")),console.log(i.gray(" - README.md")),console.log(i.gray(" - .gitignore")),console.log(i.white(`
|
|
@@ -270,7 +625,7 @@ __pycache__/
|
|
|
270
625
|
\u{1F4DD} Files to create:`)),console.log(i.gray(" - pyproject.toml (Poetry) or .venv/ (venv)")),console.log(i.gray(" - README.md")),console.log(i.gray(" - .gitignore")),console.log(i.white(`
|
|
271
626
|
\u{1F3AF} Next steps after creation:`)),console.log(i.gray(" 1. Install RapidKit Python package")),console.log(i.gray(" 2. Create projects with rapidkit CLI")),console.log(i.gray(" 3. Add modules and customize"))),console.log(i.white(`
|
|
272
627
|
\u{1F4A1} To proceed with actual creation, run without --dry-run flag
|
|
273
|
-
`));}var me=fileURLToPath(import.meta.url),
|
|
628
|
+
`));}var me=fileURLToPath(import.meta.url),ue=h.dirname(me);async function q(t,e){let o=z("Generating FastAPI demo project...").start();try{let r=h.resolve(ue,".."),a=h.join(r,"templates","kits","fastapi-standard"),m=le.configure(a,{autoescape:!1,trimBlocks:!0,lstripBlocks:!0}),d={project_name:e.project_name,author:e.author||"RapidKit User",description:e.description||"FastAPI service generated with RapidKit",app_version:e.app_version||"0.1.0",license:e.license||"MIT"},n=["src/main.py.j2","src/__init__.py.j2","src/cli.py.j2","src/routing/__init__.py.j2","src/routing/health.py.j2","src/modules/__init__.py.j2","tests/__init__.py.j2","README.md.j2","pyproject.toml.j2",".rapidkit/project.json.j2",".rapidkit/cli.py.j2",".rapidkit/rapidkit.j2"];for(let f of n){let g=h.join(a,f),v=await promises.readFile(g,"utf-8"),K=m.renderString(v,d),b=f.replace(/\.j2$/,""),D=h.join(t,b);await promises.mkdir(h.dirname(D),{recursive:!0}),await promises.writeFile(D,K),(b===".rapidkit/rapidkit"||b===".rapidkit/cli.py")&&await promises.chmod(D,493);}await promises.writeFile(h.join(t,".gitignore"),`# Python
|
|
274
629
|
__pycache__/
|
|
275
630
|
*.py[cod]
|
|
276
631
|
*$py.class
|
|
@@ -317,6 +672,10 @@ ${i.green("\u2728 Demo project created successfully!")}
|
|
|
317
672
|
|
|
318
673
|
${i.bold("\u{1F4C2} Project structure:")}
|
|
319
674
|
${t}/
|
|
675
|
+
\u251C\u2500\u2500 .rapidkit/ # RapidKit project config
|
|
676
|
+
\u2502 \u251C\u2500\u2500 project.json # Project metadata
|
|
677
|
+
\u2502 \u251C\u2500\u2500 cli.py # Local CLI handler
|
|
678
|
+
\u2502 \u2514\u2500\u2500 rapidkit # Local launcher
|
|
320
679
|
\u251C\u2500\u2500 src/
|
|
321
680
|
\u2502 \u251C\u2500\u2500 main.py # FastAPI application
|
|
322
681
|
\u2502 \u251C\u2500\u2500 cli.py # CLI commands
|
|
@@ -327,29 +686,32 @@ ${t}/
|
|
|
327
686
|
\u2514\u2500\u2500 README.md
|
|
328
687
|
|
|
329
688
|
${i.bold("\u{1F680} Get started:")}
|
|
330
|
-
cd ${
|
|
689
|
+
cd ${h.basename(t)}
|
|
331
690
|
poetry install
|
|
332
|
-
poetry run python -m src.main
|
|
691
|
+
rapidkit dev # or: poetry run python -m src.main
|
|
333
692
|
|
|
334
|
-
${i.bold("\u{1F4DA}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
693
|
+
${i.bold("\u{1F4DA} Available commands:")}
|
|
694
|
+
rapidkit init # Install dependencies
|
|
695
|
+
rapidkit dev # Start dev server with hot reload
|
|
696
|
+
rapidkit start # Start production server
|
|
697
|
+
rapidkit test # Run tests
|
|
698
|
+
rapidkit lint # Lint code
|
|
699
|
+
rapidkit format # Format code
|
|
338
700
|
|
|
339
701
|
${i.yellow("Note:")} This is a standalone demo. For full RapidKit features and modules,
|
|
340
702
|
install RapidKit Python package: ${i.cyan("pipx install rapidkit")}
|
|
341
|
-
`);}catch(r){throw o.fail("Failed to generate demo project"),r}}var ve="rapidkit",ke=fileURLToPath(import.meta.url),
|
|
703
|
+
`);}catch(r){throw o.fail("Failed to generate demo project"),r}}var ve="rapidkit",ke=fileURLToPath(import.meta.url),_e=dirname(ke),Re=join(_e,"..","package.json"),be=JSON.parse(readFileSync(Re,"utf-8")),L=be.version;async function H(){try{s.debug("Checking for updates...");let{stdout:t}=await execa("npm",["view",ve,"version"],{timeout:3e3}),e=t.trim();e&&e!==L?(console.log(i.yellow(`
|
|
342
704
|
\u26A0\uFE0F Update available: ${L} \u2192 ${e}`)),console.log(i.cyan(`Run: npm install -g rapidkit@latest
|
|
343
|
-
`))):s.debug("You are using the latest version");}catch{s.debug("Could not check for updates");}}function J(){return L}function V(t){let e=
|
|
705
|
+
`))):s.debug("You are using the latest version");}catch{s.debug("Could not check for updates");}}function J(){return L}function V(t){let e=Pe(t);if(!e.validForNewPackages){let r=e.errors||[],a=e.warnings||[],m=[...r,...a];throw new k(t,`NPM validation failed: ${m.join(", ")}`)}if(!/^[a-z][a-z0-9_-]*$/.test(t))throw new k(t,"Must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores");if(["test","tests","src","dist","build","lib","python","pip","poetry","node","npm","rapidkit","rapidkit"].includes(t.toLowerCase()))throw new k(t,`"${t}" is a reserved name. Please choose a different name.`);if(t.length<2)throw new k(t,"Name must be at least 2 characters long");if(t.length>214)throw new k(t,"Name must be less than 214 characters");return true}var w=null,O=false,Q=new Command;Q.name("rapidkit").description("Create a RapidKit development environment or workspace").version(J()).argument("[directory-name]","Name of the workspace or project directory").option("--skip-git","Skip git initialization").option("--test-mode","Install RapidKit from local path (for development/testing only)").option("--demo","Create workspace with demo kit templates (no Python installation required)").option("--demo-only","Generate a demo project in current directory (used by demo workspace)").option("--debug","Enable debug logging").option("--dry-run","Show what would be created without creating it").option("--no-update-check","Skip checking for updates").action(async(t,e)=>{try{e.debug&&(s.setDebug(!0),s.debug("Debug mode enabled"));let o=await W();if(s.debug("User config loaded",o),e.updateCheck!==!1&&await H(),console.log(i.blue.bold(`
|
|
344
706
|
\u{1F680} Welcome to RapidKit!
|
|
345
|
-
`)),e.demoOnly){let
|
|
346
|
-
\u274C ${
|
|
347
|
-
`),process.exit(1)),
|
|
707
|
+
`)),e.demoOnly){let a=t||"my-fastapi-project";try{V(a);}catch(n){throw n instanceof u&&(s.error(`
|
|
708
|
+
\u274C ${n.message}`),n.details&&s.warn(`\u{1F4A1} ${n.details}
|
|
709
|
+
`),process.exit(1)),n}let m=h.resolve(process.cwd(),a);if(w=m,e.dryRun){console.log(i.cyan(`
|
|
348
710
|
\u{1F50D} Dry-run mode - showing what would be created:
|
|
349
711
|
`)),console.log(i.white("\u{1F4C2} Project path:"),m),console.log(i.white("\u{1F4E6} Project type:"),"FastAPI demo project"),console.log(i.white("\u{1F4DD} Files to create:")),console.log(i.gray(" - src/main.py")),console.log(i.gray(" - src/cli.py")),console.log(i.gray(" - src/routing/")),console.log(i.gray(" - tests/")),console.log(i.gray(" - pyproject.toml")),console.log(i.gray(` - README.md
|
|
350
|
-
`));return}let
|
|
351
|
-
\u274C ${
|
|
352
|
-
`),process.exit(1)),
|
|
712
|
+
`));return}let d=await oe.prompt([{type:"input",name:"project_name",message:"Project name (snake_case):",default:a.replace(/-/g,"_"),validate:n=>/^[a-z][a-z0-9_]*$/.test(n)?!0:"Please use snake_case (lowercase with underscores)"},{type:"input",name:"author",message:"Author name:",default:process.env.USER||"RapidKit User"},{type:"input",name:"description",message:"Project description:",default:"FastAPI service generated with RapidKit"}]);await q(m,d);return}let r=t||"rapidkit-workspace";try{V(r);}catch(a){throw a instanceof u&&(s.error(`
|
|
713
|
+
\u274C ${a.message}`),a.details&&s.warn(`\u{1F4A1} ${a.details}
|
|
714
|
+
`),process.exit(1)),a}if(w=h.resolve(process.cwd(),r),e.demo){console.log(i.gray(`This will create a workspace with demo kit templates.
|
|
353
715
|
You can generate demo projects inside without installing Python RapidKit.
|
|
354
716
|
`)),await U(r,{skipGit:e.skipGit||o.skipGit,testMode:!1,demoMode:!0,dryRun:e.dryRun,userConfig:o});return}console.log(i.yellow.bold(`\u26A0\uFE0F BETA NOTICE
|
|
355
717
|
`)),console.log(i.yellow(`RapidKit Python package is not yet available on PyPI.
|
|
@@ -359,8 +721,8 @@ Full installation mode will be available soon.
|
|
|
359
721
|
`)),console.log(i.white(" 2. Test mode (if you have local RapidKit):")),console.log(i.gray(` npx rapidkit my-workspace --test-mode
|
|
360
722
|
`)),e.testMode||(console.log(i.red(`\u274C Cannot proceed without --demo or --test-mode flag.
|
|
361
723
|
`)),process.exit(1)),console.log(i.yellow(`\u26A0\uFE0F Running in TEST MODE - Installing from local path
|
|
362
|
-
`)),await U(r,{skipGit:e.skipGit||o.skipGit,testMode:e.testMode,demoMode:!1,dryRun:e.dryRun,userConfig:o});}catch(o){o instanceof
|
|
724
|
+
`)),await U(r,{skipGit:e.skipGit||o.skipGit,testMode:e.testMode,demoMode:!1,dryRun:e.dryRun,userConfig:o});}catch(o){o instanceof u?(s.error(`
|
|
363
725
|
\u274C ${o.message}`),o.details&&s.warn(`\u{1F4A1} ${o.details}`),s.debug("Error code:",o.code)):(s.error(`
|
|
364
|
-
\u274C An unexpected error occurred:`),console.error(o)),process.exit(1);}finally{w=null;}});process.on("SIGINT",async()=>{if(!
|
|
726
|
+
\u274C An unexpected error occurred:`),console.error(o)),process.exit(1);}finally{w=null;}});process.on("SIGINT",async()=>{if(!O){if(O=true,console.log(i.yellow(`
|
|
365
727
|
|
|
366
|
-
\u26A0\uFE0F Interrupted by user`)),w&&await
|
|
728
|
+
\u26A0\uFE0F Interrupted by user`)),w&&await y.pathExists(w)){console.log(i.gray("Cleaning up partial installation..."));try{await y.remove(w),console.log(i.green("\u2713 Cleanup complete"));}catch(t){s.debug("Cleanup failed:",t);}}process.exit(130);}});process.on("SIGTERM",async()=>{if(!O){if(O=true,s.debug("Received SIGTERM"),w&&await y.pathExists(w))try{await y.remove(w);}catch(t){s.debug("Cleanup failed:",t);}process.exit(143);}});Q.parse();
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""RapidKit CLI wrapper (npm demo template)
|
|
3
|
+
|
|
4
|
+
This file provides local project commands for demo projects generated
|
|
5
|
+
via npx rapidkit --demo. It mimics the behavior of the full RapidKit
|
|
6
|
+
engine for dev, start, test, lint, and format commands.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import socket
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _print_banner(emoji: str, message: str) -> None:
|
|
18
|
+
print(f"{emoji} {message}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _run(*cmd: Any, cwd: Path | str | None = None, env: dict | None = None) -> int:
|
|
22
|
+
try:
|
|
23
|
+
run_env = None
|
|
24
|
+
if env is not None:
|
|
25
|
+
import os as _os
|
|
26
|
+
run_env = _os.environ.copy()
|
|
27
|
+
run_env.update(env)
|
|
28
|
+
|
|
29
|
+
proc = subprocess.run([str(c) for c in cmd], cwd=str(cwd) if cwd else None, env=run_env)
|
|
30
|
+
return proc.returncode
|
|
31
|
+
except KeyboardInterrupt:
|
|
32
|
+
print("\n🛑 Command interrupted by user")
|
|
33
|
+
return 130
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
print(f"❌ Error running command: {exc}")
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _project_type(root: Path) -> str:
|
|
40
|
+
if (root / "pyproject.toml").exists():
|
|
41
|
+
return "python"
|
|
42
|
+
if (root / "package.json").exists():
|
|
43
|
+
return "node"
|
|
44
|
+
return "unknown"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _python_module(module: str, *module_args: str) -> int:
|
|
48
|
+
return _run(sys.executable, "-m", module, *module_args)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _python_code_targets(root: Path) -> list[str]:
|
|
52
|
+
targets: list[str] = []
|
|
53
|
+
for name in ("src", "tests"):
|
|
54
|
+
if (root / name).exists():
|
|
55
|
+
targets.append(name)
|
|
56
|
+
return targets or ["src"]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _venv_has_uvicorn(venv_path: Path) -> bool:
|
|
60
|
+
if (venv_path / "bin" / "uvicorn").exists():
|
|
61
|
+
return True
|
|
62
|
+
for p in venv_path.rglob("site-packages/*"):
|
|
63
|
+
name = p.name.lower()
|
|
64
|
+
if "uvicorn" in name or "fastapi" in name:
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dev(port: int = 8000, host: str = "0.0.0.0", allow_global_runtime: bool = False) -> None:
|
|
70
|
+
"""Start development server with reload"""
|
|
71
|
+
_print_banner("🚀", "Starting development server with hot reload...")
|
|
72
|
+
root = Path.cwd()
|
|
73
|
+
ptype = _project_type(root)
|
|
74
|
+
|
|
75
|
+
if ptype == "python":
|
|
76
|
+
venv_dir = root / ".venv"
|
|
77
|
+
|
|
78
|
+
if venv_dir.exists():
|
|
79
|
+
if not _venv_has_uvicorn(venv_dir):
|
|
80
|
+
print("❌ Project .venv was found but uvicorn/fastapi doesn't appear installed.")
|
|
81
|
+
print("💡 Run 'poetry install' to install dependencies.")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
else:
|
|
84
|
+
if not allow_global_runtime:
|
|
85
|
+
print("❌ Project environment not bootstrapped (no .venv found).")
|
|
86
|
+
print("")
|
|
87
|
+
print("💡 Initialize and install dependencies with:")
|
|
88
|
+
print("")
|
|
89
|
+
print(" poetry install")
|
|
90
|
+
print("")
|
|
91
|
+
print("If you intentionally want to use the system Python, run:")
|
|
92
|
+
print("")
|
|
93
|
+
print(" rapidkit dev --allow-global-runtime")
|
|
94
|
+
print("")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
if shutil.which("uvicorn") is None:
|
|
98
|
+
print("❌ No uvicorn executable found on PATH and no .venv present.")
|
|
99
|
+
print("")
|
|
100
|
+
print("💡 Install dependencies with: poetry install")
|
|
101
|
+
print("")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
print("⚠️ Running with system/global Python runtime.")
|
|
104
|
+
|
|
105
|
+
rc = _run(sys.executable, "-m", "uvicorn", "src.main:app", "--reload", "--host", host, "--port", str(port))
|
|
106
|
+
if rc != 0:
|
|
107
|
+
sys.exit(rc)
|
|
108
|
+
else:
|
|
109
|
+
print("❌ Unknown project type. Ensure pyproject.toml exists.")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def init() -> None:
|
|
114
|
+
"""Bootstrap project: install dependencies"""
|
|
115
|
+
_print_banner("🚀", "Bootstrapping project (installing dependencies)")
|
|
116
|
+
root = Path.cwd()
|
|
117
|
+
ptype = _project_type(root)
|
|
118
|
+
|
|
119
|
+
if ptype == "python":
|
|
120
|
+
if shutil.which("poetry"):
|
|
121
|
+
rc = _run("poetry", "install")
|
|
122
|
+
if rc != 0:
|
|
123
|
+
print("❌ Failed to install dependencies via poetry.")
|
|
124
|
+
sys.exit(rc)
|
|
125
|
+
print("✅ Dependencies installed successfully!")
|
|
126
|
+
else:
|
|
127
|
+
print("❌ Poetry not found. Install it with: pip install poetry")
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
else:
|
|
130
|
+
print("❌ Unknown project type. Ensure pyproject.toml exists.")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def start(port: int = 8000, host: str = "0.0.0.0", allow_global_runtime: bool = False) -> None:
|
|
135
|
+
"""Start production server"""
|
|
136
|
+
_print_banner("⚡", "Starting production server...")
|
|
137
|
+
root = Path.cwd()
|
|
138
|
+
ptype = _project_type(root)
|
|
139
|
+
|
|
140
|
+
if ptype == "python":
|
|
141
|
+
venv_dir = root / ".venv"
|
|
142
|
+
if venv_dir.exists():
|
|
143
|
+
if not _venv_has_uvicorn(venv_dir):
|
|
144
|
+
print("❌ Project .venv was found but uvicorn/fastapi doesn't appear installed.")
|
|
145
|
+
print("💡 Run 'poetry install' to install dependencies.")
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
else:
|
|
148
|
+
if not allow_global_runtime:
|
|
149
|
+
print("❌ Project environment not bootstrapped (no .venv found).")
|
|
150
|
+
print("💡 Run 'poetry install' to install dependencies.")
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
rc = _run(sys.executable, "-m", "uvicorn", "src.main:app", "--host", host, "--port", str(port))
|
|
154
|
+
if rc != 0:
|
|
155
|
+
sys.exit(rc)
|
|
156
|
+
else:
|
|
157
|
+
print("❌ Unknown project type.")
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def build() -> None:
|
|
162
|
+
"""Build project"""
|
|
163
|
+
_print_banner("📦", "Building project")
|
|
164
|
+
rc = _python_module("build")
|
|
165
|
+
if rc != 0:
|
|
166
|
+
sys.exit(rc)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test() -> None:
|
|
170
|
+
"""Run tests"""
|
|
171
|
+
_print_banner("🧪", "Running tests")
|
|
172
|
+
rc = _python_module("pytest", "-q")
|
|
173
|
+
if rc != 0:
|
|
174
|
+
sys.exit(rc)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def lint() -> None:
|
|
178
|
+
"""Run linting"""
|
|
179
|
+
_print_banner("🔧", "Running lint")
|
|
180
|
+
root = Path.cwd()
|
|
181
|
+
targets = _python_code_targets(root)
|
|
182
|
+
rc = _python_module("ruff", "check", *targets)
|
|
183
|
+
if rc == 0:
|
|
184
|
+
rc = _python_module("black", "--check", *targets)
|
|
185
|
+
if rc != 0:
|
|
186
|
+
sys.exit(rc)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def format_code() -> None:
|
|
190
|
+
"""Format code"""
|
|
191
|
+
_print_banner("✨", "Formatting")
|
|
192
|
+
root = Path.cwd()
|
|
193
|
+
targets = _python_code_targets(root)
|
|
194
|
+
rc = _python_module("ruff", "check", *targets, "--fix")
|
|
195
|
+
if rc == 0:
|
|
196
|
+
rc = _python_module("black", *targets)
|
|
197
|
+
if rc != 0:
|
|
198
|
+
sys.exit(rc)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def help_cmd() -> None:
|
|
202
|
+
"""Show help"""
|
|
203
|
+
_print_banner("📚", "Project Commands")
|
|
204
|
+
print("Usage: rapidkit <command> [args...]\n")
|
|
205
|
+
print(" init 📦 Initialize project (install deps)")
|
|
206
|
+
print(" dev 🚀 Start development server")
|
|
207
|
+
print(" start ⚡ Start production server")
|
|
208
|
+
print(" build 📦 Build for production")
|
|
209
|
+
print(" test 🧪 Run tests")
|
|
210
|
+
print(" lint 🔧 Lint code")
|
|
211
|
+
print(" format ✨ Format code")
|
|
212
|
+
print(" help 📚 Show help")
|
|
213
|
+
print("")
|
|
214
|
+
print("💡 Note: This is a demo project. For full RapidKit features:")
|
|
215
|
+
print(" pipx install rapidkit")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def main():
|
|
219
|
+
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "help"):
|
|
220
|
+
help_cmd()
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
command = sys.argv[1]
|
|
224
|
+
args = sys.argv[2:]
|
|
225
|
+
|
|
226
|
+
commands = {
|
|
227
|
+
"init": init,
|
|
228
|
+
"dev": dev,
|
|
229
|
+
"start": start,
|
|
230
|
+
"build": build,
|
|
231
|
+
"test": test,
|
|
232
|
+
"lint": lint,
|
|
233
|
+
"format": format_code,
|
|
234
|
+
"help": help_cmd,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if command not in commands:
|
|
238
|
+
print(f"❌ Unknown command: {command}")
|
|
239
|
+
help_cmd()
|
|
240
|
+
sys.exit(1)
|
|
241
|
+
|
|
242
|
+
if command in ("dev", "start"):
|
|
243
|
+
import argparse as _arg
|
|
244
|
+
_parser = _arg.ArgumentParser(prog=f"rapidkit {command}")
|
|
245
|
+
_parser.add_argument("-p", "--port", dest="port", type=int, default=8000)
|
|
246
|
+
_parser.add_argument("--host", dest="host", default="0.0.0.0")
|
|
247
|
+
_parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
|
|
248
|
+
_ns, _extra = _parser.parse_known_args(args)
|
|
249
|
+
commands[command](port=_ns.port, host=_ns.host, allow_global_runtime=_ns.allow_global_runtime)
|
|
250
|
+
else:
|
|
251
|
+
commands[command]()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
main()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Local RapidKit project launcher (npm demo template)
|
|
3
|
+
# This script provides local project commands for demo projects.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
+
|
|
8
|
+
# Detect project type
|
|
9
|
+
if [ -f "$ROOT_DIR/pyproject.toml" ]; then
|
|
10
|
+
PKG_TYPE="python"
|
|
11
|
+
elif [ -f "$ROOT_DIR/package.json" ]; then
|
|
12
|
+
PKG_TYPE="node"
|
|
13
|
+
else
|
|
14
|
+
PKG_TYPE="unknown"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
VENV_PY="${ROOT_DIR}/.venv/bin/python"
|
|
18
|
+
VENV_POETRY="${ROOT_DIR}/.venv/bin/poetry"
|
|
19
|
+
|
|
20
|
+
case "$PKG_TYPE" in
|
|
21
|
+
python)
|
|
22
|
+
# prefer project-local poetry
|
|
23
|
+
if [ -x "$VENV_POETRY" ]; then
|
|
24
|
+
if [ "${1:-}" = "init" ]; then
|
|
25
|
+
exec "$VENV_POETRY" "install"
|
|
26
|
+
else
|
|
27
|
+
exec "$VENV_POETRY" "run" "$@"
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# then prefer poetry on PATH
|
|
32
|
+
if command -v poetry >/dev/null 2>&1; then
|
|
33
|
+
if [ "${1:-}" = "init" ]; then
|
|
34
|
+
exec poetry "install"
|
|
35
|
+
else
|
|
36
|
+
exec poetry "run" "$@"
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# If venv python exists, try installing poetry into it
|
|
41
|
+
if [ -x "$VENV_PY" ]; then
|
|
42
|
+
echo "⚠️ Poetry not found. Installing poetry into project .venv..."
|
|
43
|
+
"$VENV_PY" -m pip install --upgrade pip >/dev/null 2>&1 || true
|
|
44
|
+
"$VENV_PY" -m pip install poetry >/dev/null 2>&1 || true
|
|
45
|
+
if [ -x "$VENV_POETRY" ]; then
|
|
46
|
+
if [ "${1:-}" = "init" ]; then
|
|
47
|
+
exec "$VENV_POETRY" "install"
|
|
48
|
+
else
|
|
49
|
+
exec "$VENV_POETRY" "run" "$@"
|
|
50
|
+
fi
|
|
51
|
+
else
|
|
52
|
+
echo "❌ Failed to locate poetry after installation."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "❌ Poetry is not available and no project .venv exists."
|
|
58
|
+
echo "💡 Run: 'poetry install' to bootstrap the project, or install poetry: 'pip install poetry'"
|
|
59
|
+
exit 127
|
|
60
|
+
;;
|
|
61
|
+
|
|
62
|
+
*)
|
|
63
|
+
echo "❌ Could not detect project type (pyproject.toml missing)."
|
|
64
|
+
echo "💡 This appears to be a demo project. Run 'poetry install' to set up."
|
|
65
|
+
exit 127
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
@@ -5,17 +5,18 @@ A minimal FastAPI service generated with the **FastAPI Standard Kit**. All domai
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
rapidkit init # bootstrap the project (preferred)
|
|
9
|
+
rapidkit dev
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
## Available commands
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
rapidkit init # 🔧 Install dependencies
|
|
16
|
+
rapidkit dev # 🚀 Start development server with hot reload
|
|
17
|
+
rapidkit start # ⚡ Start production server
|
|
18
|
+
rapidkit test # 🧪 Run tests
|
|
19
|
+
rapidkit help # 📚 Show available commands
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
## Project layout
|
|
@@ -95,7 +95,8 @@ def dev() -> None:
|
|
|
95
95
|
"""🚀 Start development server with hot reload
|
|
96
96
|
|
|
97
97
|
Usage:
|
|
98
|
-
|
|
98
|
+
rapidkit dev [options] # preferred (project-aware)
|
|
99
|
+
# or: poetry run dev [options]
|
|
99
100
|
|
|
100
101
|
Options:
|
|
101
102
|
--port, -p PORT Server port (default: 8000)
|
|
@@ -103,9 +104,10 @@ def dev() -> None:
|
|
|
103
104
|
--env, -e ENV Environment (default: development)
|
|
104
105
|
|
|
105
106
|
Examples:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
rapidkit dev
|
|
108
|
+
rapidkit dev --port 3000
|
|
109
|
+
rapidkit dev --host 127.0.0.1 --port 8080
|
|
110
|
+
# or: poetry run dev --port 3000
|
|
109
111
|
"""
|
|
110
112
|
_print_banner("🚀", "Starting development server with hot reload...")
|
|
111
113
|
_print_banner("📁", f"Working directory: {Path.cwd()}")
|
|
@@ -175,7 +177,7 @@ def build() -> None:
|
|
|
175
177
|
_print_error(f"Build failed: {e}")
|
|
176
178
|
sys.exit(1)
|
|
177
179
|
except FileNotFoundError:
|
|
178
|
-
_print_error("Build tools not found. Install with: poetry install --group dev")
|
|
180
|
+
_print_error("Build tools not found. Install with: rapidkit init (or: poetry install --group dev)")
|
|
179
181
|
sys.exit(1)
|
|
180
182
|
|
|
181
183
|
|
|
@@ -214,7 +216,7 @@ def test() -> None:
|
|
|
214
216
|
sys.exit(result.returncode)
|
|
215
217
|
|
|
216
218
|
except FileNotFoundError:
|
|
217
|
-
_print_error("pytest not found. Install with: poetry install --group dev")
|
|
219
|
+
_print_error("pytest not found. Install with: rapidkit init (or: poetry install --group dev)")
|
|
218
220
|
sys.exit(1)
|
|
219
221
|
|
|
220
222
|
|
|
@@ -247,7 +249,7 @@ def lint() -> None:
|
|
|
247
249
|
_print_error("Linting failed! Run 'poetry run format' to fix issues.")
|
|
248
250
|
sys.exit(1)
|
|
249
251
|
except FileNotFoundError:
|
|
250
|
-
_print_error("Linting tools not found. Install with: poetry install --group dev")
|
|
252
|
+
_print_error("Linting tools not found. Install with: rapidkit init (or: poetry install --group dev)")
|
|
251
253
|
sys.exit(1)
|
|
252
254
|
|
|
253
255
|
|
|
@@ -280,7 +282,7 @@ def format() -> None:
|
|
|
280
282
|
_print_error(f"Formatting failed: {e}")
|
|
281
283
|
sys.exit(1)
|
|
282
284
|
except FileNotFoundError:
|
|
283
|
-
_print_error("Formatting tools not found. Install with: poetry install --group dev")
|
|
285
|
+
_print_error("Formatting tools not found. Install with: rapidkit init (or: poetry install --group dev)")
|
|
284
286
|
sys.exit(1)
|
|
285
287
|
|
|
286
288
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from typing import AsyncIterator
|
|
7
|
+
|
|
5
8
|
from fastapi import FastAPI
|
|
6
9
|
from fastapi.middleware.cors import CORSMiddleware
|
|
7
10
|
|
|
@@ -9,12 +12,24 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
9
12
|
|
|
10
13
|
from .routing import api_router
|
|
11
14
|
|
|
15
|
+
|
|
16
|
+
@asynccontextmanager
|
|
17
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
18
|
+
"""Application lifespan context manager for startup/shutdown events."""
|
|
19
|
+
# Startup
|
|
20
|
+
# <<<inject:startup>>>
|
|
21
|
+
yield
|
|
22
|
+
# Shutdown
|
|
23
|
+
# <<<inject:shutdown>>>
|
|
24
|
+
|
|
25
|
+
|
|
12
26
|
app = FastAPI(
|
|
13
27
|
title="{{ project_name }}",
|
|
14
28
|
description="{{ description }}",
|
|
15
29
|
version="{{ app_version }}",
|
|
16
30
|
docs_url="/docs",
|
|
17
31
|
redoc_url="/redoc",
|
|
32
|
+
lifespan=lifespan,
|
|
18
33
|
)
|
|
19
34
|
|
|
20
35
|
app.add_middleware(
|
|
@@ -27,15 +42,3 @@ app.add_middleware(
|
|
|
27
42
|
|
|
28
43
|
app.include_router(api_router, prefix="/api")
|
|
29
44
|
# <<<inject:routes>>>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@app.on_event("startup")
|
|
33
|
-
async def _on_startup() -> None:
|
|
34
|
-
"""Execute startup hooks provided by modules."""
|
|
35
|
-
# <<<inject:startup>>>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@app.on_event("shutdown")
|
|
39
|
-
async def _on_shutdown() -> None:
|
|
40
|
-
"""Execute shutdown hooks provided by modules."""
|
|
41
|
-
# <<<inject:shutdown>>>
|
|
@@ -4,10 +4,29 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from fastapi import APIRouter
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
try:
|
|
8
|
+
from src.health.registry import (
|
|
9
|
+
list_registered_health_routes as _list_registered_health_routes,
|
|
10
|
+
)
|
|
11
|
+
except ImportError: # pragma: no cover - registry not generated yet
|
|
12
|
+
def _list_registered_health_routes(
|
|
13
|
+
prefix: str = "/api/health",
|
|
14
|
+
) -> list[dict[str, str]]:
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
router = APIRouter(prefix="/health", tags=["health"])
|
|
8
19
|
|
|
9
20
|
|
|
10
21
|
@router.get("/", summary="Health check")
|
|
11
22
|
async def heartbeat() -> dict[str, str]:
|
|
12
23
|
"""Return basic service heartbeat."""
|
|
24
|
+
|
|
13
25
|
return {"status": "ok"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.get("/modules", summary="Registered module health endpoints")
|
|
29
|
+
async def module_health_catalog() -> dict[str, list[dict[str, str]]]:
|
|
30
|
+
"""Expose metadata for module-provided health routers."""
|
|
31
|
+
|
|
32
|
+
return {"routes": _list_registered_health_routes()}
|