workspai 0.43.0
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/LICENSE +21 -0
- package/README.md +624 -0
- package/contracts/agent-customization-pack.v1.json +229 -0
- package/contracts/analyze-last-run.v1.json +106 -0
- package/contracts/artifact-remediation-plan.v1.json +163 -0
- package/contracts/backend-import-stack-parity.snapshot.json +37 -0
- package/contracts/cli-log-event.v1.json +51 -0
- package/contracts/create-planner-capabilities.v1.json +251 -0
- package/contracts/doctor-project-evidence.v1.json +304 -0
- package/contracts/doctor-remediation-plan.v1.json +496 -0
- package/contracts/doctor-workspace-evidence.v1.json +315 -0
- package/contracts/extension-cli-compatibility.v1.json +26 -0
- package/contracts/infra-stack.v1.json +305 -0
- package/contracts/module-layout.v1.json +34 -0
- package/contracts/module-support.v1.json +20 -0
- package/contracts/pipeline-last-run.v1.json +80 -0
- package/contracts/release-readiness.v1.json +60 -0
- package/contracts/runtime-command-surface.v1.json +364 -0
- package/contracts/workspace-intelligence/agent-action-outcome.v1.json +38 -0
- package/contracts/workspace-intelligence/blocker-resolution.v1.json +65 -0
- package/contracts/workspace-intelligence/doctor-fix-result.v1.json +44 -0
- package/contracts/workspace-intelligence/fact-freshness.v1.json +58 -0
- package/contracts/workspace-intelligence/studio-blocker-handoff.v1.json +115 -0
- package/contracts/workspace-intelligence/workspace-context.v1.json +142 -0
- package/contracts/workspace-intelligence/workspace-contract-verify.v1.json +51 -0
- package/contracts/workspace-intelligence/workspace-dependency-graph.v1.json +152 -0
- package/contracts/workspace-intelligence/workspace-explain.v1.json +31 -0
- package/contracts/workspace-intelligence/workspace-impact.v1.json +114 -0
- package/contracts/workspace-intelligence/workspace-intelligence-history.v1.json +40 -0
- package/contracts/workspace-intelligence/workspace-model-diff.v1.json +105 -0
- package/contracts/workspace-intelligence/workspace-model-snapshot.v1.json +36 -0
- package/contracts/workspace-intelligence/workspace-model.v1.json +135 -0
- package/contracts/workspace-intelligence/workspace-operational-skill.v1.json +37 -0
- package/contracts/workspace-intelligence/workspace-skills-index.v1.json +27 -0
- package/contracts/workspace-intelligence/workspace-verify.v1.json +213 -0
- package/contracts/workspace-registry.v1.json +83 -0
- package/contracts/workspace-run-last.v1.json +112 -0
- package/data/modules-embeddings.json +16953 -0
- package/dist/analyze-5BU4QIPK.js +1 -0
- package/dist/artifact-remediation-plan-5GJWPPRS.js +3 -0
- package/dist/autopilot-release-QYUOVB26.js +1 -0
- package/dist/chunk-22DT744Z.js +1 -0
- package/dist/chunk-2E6WGUN6.js +1 -0
- package/dist/chunk-2FIX2MDC.js +1 -0
- package/dist/chunk-3KXW53QI.js +1 -0
- package/dist/chunk-3ZK2GU7C.js +1 -0
- package/dist/chunk-4KUIFXHM.js +5 -0
- package/dist/chunk-54XJNLYC.js +13 -0
- package/dist/chunk-5SWNSJGW.js +1 -0
- package/dist/chunk-5XATWNME.js +1 -0
- package/dist/chunk-753LOKFZ.js +2 -0
- package/dist/chunk-7YHK5NM3.js +2 -0
- package/dist/chunk-A5FBGRJA.js +1 -0
- package/dist/chunk-AAKUNWPR.js +78 -0
- package/dist/chunk-AFSZHLQT.js +1 -0
- package/dist/chunk-AQ4XZZC6.js +1 -0
- package/dist/chunk-CVTCPPEF.js +1 -0
- package/dist/chunk-DNAY5ALC.js +1 -0
- package/dist/chunk-EWYYLJQY.js +2 -0
- package/dist/chunk-GSWPGELT.js +2 -0
- package/dist/chunk-GZTYAEWX.js +1 -0
- package/dist/chunk-HTYMXMQM.js +1 -0
- package/dist/chunk-IBJEQHJR.js +205 -0
- package/dist/chunk-ICGWHIMK.js +1 -0
- package/dist/chunk-JEI6BTZI.js +3 -0
- package/dist/chunk-JHC6SCJC.js +1 -0
- package/dist/chunk-LO4C4VXR.js +4 -0
- package/dist/chunk-LPZFK3Y6.js +920 -0
- package/dist/chunk-LUP4U6EE.js +1 -0
- package/dist/chunk-LYC6AIF2.js +2 -0
- package/dist/chunk-LZQNVH7D.js +1 -0
- package/dist/chunk-NAAQMSO3.js +1 -0
- package/dist/chunk-NFUXULIF.js +1 -0
- package/dist/chunk-OCGZNSOE.js +1 -0
- package/dist/chunk-OJEWQO3Z.js +33 -0
- package/dist/chunk-OL64GWQC.js +9 -0
- package/dist/chunk-PI5KUXDA.js +7 -0
- package/dist/chunk-PYCJWW4B.js +1 -0
- package/dist/chunk-RE773WOX.js +13 -0
- package/dist/chunk-RO73VVHW.js +2 -0
- package/dist/chunk-S5KTATOU.js +1 -0
- package/dist/chunk-SI3AQKIP.js +1 -0
- package/dist/chunk-T3GJ7YVG.js +1 -0
- package/dist/chunk-T6OR6YOI.js +2 -0
- package/dist/chunk-VRDF2J46.js +653 -0
- package/dist/chunk-VZKSNUEE.js +2 -0
- package/dist/chunk-WU4ZRRA5.js +4 -0
- package/dist/chunk-XEJD4KCV.js +33 -0
- package/dist/chunk-XZGVNGRB.js +1 -0
- package/dist/chunk-Y45WZR5N.js +5 -0
- package/dist/create-WRZO4HK2.js +1 -0
- package/dist/demo-kit-AJ5LIM5G.js +149 -0
- package/dist/doctor-MF3M3FS3.js +1 -0
- package/dist/dotnet-webapi-clean-IPY3MCGQ.js +425 -0
- package/dist/gofiber-standard-N5N3QGH3.js +1397 -0
- package/dist/gogin-standard-LAWQTBWL.js +1459 -0
- package/dist/imported-projects-registry-FOIE27WT.js +1 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +454 -0
- package/dist/managed-agent-markers-AXUM75OE.js +8 -0
- package/dist/module-layout-NZ43RSC5.js +1 -0
- package/dist/pipeline-QCUHFWDY.js +5 -0
- package/dist/platform-capabilities-XAVU4HSM.js +1 -0
- package/dist/pythonRapidkitExec-3L3XJBSY.js +1 -0
- package/dist/springboot-standard-OZYF7ZM7.js +697 -0
- package/dist/workspace-PR6VZRBG.js +1 -0
- package/dist/workspace-agent-sync-KTYF3HQR.js +1 -0
- package/dist/workspace-archive-GCZNI44O.js +3 -0
- package/dist/workspace-context-QUTOP2XV.js +1 -0
- package/dist/workspace-contract-XUGMCUSQ.js +1 -0
- package/dist/workspace-explain-JEONEGZZ.js +1 -0
- package/dist/workspace-explain-contract-DNS2YEL7.js +1 -0
- package/dist/workspace-feedback-R6H4UFZB.js +1 -0
- package/dist/workspace-foundation-KGSV4E6D.js +1 -0
- package/dist/workspace-graph-2A5THUCI.js +1 -0
- package/dist/workspace-history-CGCK2DJI.js +1 -0
- package/dist/workspace-intelligence-QJGVL37U.js +1 -0
- package/dist/workspace-marker-SMBC3Z2Q.js +1 -0
- package/dist/workspace-mcp-serve-AK4MYEHQ.js +3 -0
- package/dist/workspace-model-PNXXOGPO.js +1 -0
- package/dist/workspace-python-engine-state-MTWIIZPY.js +2 -0
- package/dist/workspace-registry-summary-RUJE6RPH.js +1 -0
- package/dist/workspace-run-Y7YMUIHX.js +1 -0
- package/dist/workspace-verify-WSDUUTR5.js +1 -0
- package/dist/workspace-watch-AGPOJ74Q.js +1 -0
- package/docs/AI_DYNAMIC_INTEGRATION.md +440 -0
- package/docs/AI_EXAMPLES.md +419 -0
- package/docs/AI_FEATURES.md +460 -0
- package/docs/AI_QUICKSTART.md +245 -0
- package/docs/DEVELOPMENT.md +90 -0
- package/docs/From Code to Shared Understanding.png +0 -0
- package/docs/OPEN_SOURCE_USER_SCENARIOS.md +170 -0
- package/docs/OPTIMIZATION_GUIDE.md +504 -0
- package/docs/PACKAGE_MANAGER_POLICY.md +25 -0
- package/docs/README.md +125 -0
- package/docs/SECURITY.md +63 -0
- package/docs/SETUP.md +107 -0
- package/docs/UTILITIES.md +221 -0
- package/docs/WORKSPACE_MARKER_SPEC.md +276 -0
- package/docs/ci-workflows.md +59 -0
- package/docs/commands-reference.md +172 -0
- package/docs/config-file-guide.md +299 -0
- package/docs/contracts/ARTIFACT_CATALOG.md +333 -0
- package/docs/contracts/CLI_LOG_EVENT_STREAM.md +123 -0
- package/docs/contracts/COMMAND_OWNERSHIP_MATRIX.md +138 -0
- package/docs/contracts/NAMING_AND_COEXISTENCE.md +60 -0
- package/docs/contracts/README.md +86 -0
- package/docs/contracts/RUNTIME_ACCEPTANCE_MATRIX.md +98 -0
- package/docs/contracts/RUNTIME_SUPPORT_MATRIX.md +75 -0
- package/docs/contracts/rapidkit-cli-contracts.json +239 -0
- package/docs/create-planner-capabilities.md +81 -0
- package/docs/doctor-command.md +467 -0
- package/docs/examples/ci-agent-grounding.yml +65 -0
- package/docs/from-code-to-shared-understanding.md +45 -0
- package/docs/governance-policy.enterprise.example.json +40 -0
- package/docs/mirror-config.enterprise.example.json +60 -0
- package/docs/policies.workspace.example.yml +23 -0
- package/docs/workspace-operations.md +160 -0
- package/docs/workspace-run.md +104 -0
- package/package.json +179 -0
- package/rapidkit.config.example.cjs +62 -0
- package/scripts/check-agent-customization-drift.mjs +78 -0
- package/scripts/check-cli-resolution.cjs +72 -0
- package/scripts/enforce-package-manager.cjs +10 -0
- package/scripts/enterprise-package-smoke.mjs +463 -0
- package/scripts/prepack-enterprise.mjs +43 -0
- package/templates/generator.js +179 -0
- package/templates/kits/fastapi-ddd/README.md.j2 +117 -0
- package/templates/kits/fastapi-ddd/common/env.example.j2 +10 -0
- package/templates/kits/fastapi-ddd/env.example.j2 +1 -0
- package/templates/kits/fastapi-ddd/pyproject.toml.j2 +64 -0
- package/templates/kits/fastapi-ddd/src/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/__init__.py.j2 +11 -0
- package/templates/kits/fastapi-ddd/src/app/application/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/application/interfaces.py.j2 +43 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/health.py.j2 +14 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/notes.py.j2 +24 -0
- package/templates/kits/fastapi-ddd/src/app/config/__init__.py.j2 +16 -0
- package/templates/kits/fastapi-ddd/src/app/domain/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/health.py.j2 +16 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/note.py.j2 +27 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/health.py.j2 +17 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/notes.py.j2 +28 -0
- package/templates/kits/fastapi-ddd/src/app/main.py.j2 +61 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/dependencies/__init__.py.j2 +19 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/router.py.j2 +10 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/health.py.j2 +27 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/notes.py.j2 +50 -0
- package/templates/kits/fastapi-ddd/src/app/shared/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/shared/result.py.j2 +28 -0
- package/templates/kits/fastapi-ddd/src/cli.py.j2 +170 -0
- package/templates/kits/fastapi-ddd/src/main.py.j2 +35 -0
- package/templates/kits/fastapi-ddd/src/modules/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/routing/__init__.py.j2 +13 -0
- package/templates/kits/fastapi-ddd/src/routing/health.py.j2 +7 -0
- package/templates/kits/fastapi-ddd/src/routing/notes.py.j2 +7 -0
- package/templates/kits/fastapi-ddd/tests/__init__.py.j2 +1 -0
- package/templates/kits/fastapi-ddd/tests/test_app_factory.py.j2 +22 -0
- package/templates/kits/fastapi-ddd/tests/test_health.py.j2 +17 -0
- package/templates/kits/fastapi-ddd/tests/test_notes.py.j2 +27 -0
- package/templates/kits/fastapi-standard/README.md.j2 +140 -0
- package/templates/kits/fastapi-standard/common/env.example.j2 +10 -0
- package/templates/kits/fastapi-standard/env.example.j2 +1 -0
- package/templates/kits/fastapi-standard/pyproject.toml.j2 +64 -0
- package/templates/kits/fastapi-standard/src/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/cli.py.j2 +171 -0
- package/templates/kits/fastapi-standard/src/main.py.j2 +66 -0
- package/templates/kits/fastapi-standard/src/modules/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/routing/__init__.py.j2 +16 -0
- package/templates/kits/fastapi-standard/src/routing/examples.py.j2 +71 -0
- package/templates/kits/fastapi-standard/src/routing/health.py.j2 +22 -0
- package/templates/kits/fastapi-standard/tests/__init__.py.j2 +1 -0
- package/templates/kits/fastapi-standard/tests/test_examples.py.j2 +29 -0
- package/templates/kits/fastapi-standard/tests/test_health.py.j2 +17 -0
- package/templates/kits/nestjs-standard/Dockerfile.j2 +41 -0
- package/templates/kits/nestjs-standard/README.md.j2 +134 -0
- package/templates/kits/nestjs-standard/docker-compose.yml.j2 +94 -0
- package/templates/kits/nestjs-standard/docs/README.md.j2 +15 -0
- package/templates/kits/nestjs-standard/env.example.j2 +18 -0
- package/templates/kits/nestjs-standard/eslint.config.cjs.j2 +9 -0
- package/templates/kits/nestjs-standard/jest.config.ts.j2 +28 -0
- package/templates/kits/nestjs-standard/nest-cli.json.j2 +10 -0
- package/templates/kits/nestjs-standard/package.json.j2 +101 -0
- package/templates/kits/nestjs-standard/src/app.controller.ts.j2 +14 -0
- package/templates/kits/nestjs-standard/src/app.module.ts.j2 +26 -0
- package/templates/kits/nestjs-standard/src/app.service.ts.j2 +16 -0
- package/templates/kits/nestjs-standard/src/auth/auth.controller.ts.j2 +20 -0
- package/templates/kits/nestjs-standard/src/auth/auth.module.ts.j2 +13 -0
- package/templates/kits/nestjs-standard/src/auth/auth.service.ts.j2 +6 -0
- package/templates/kits/nestjs-standard/src/auth/entities/token.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/auth/entities/user.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/auth/entities/webauthn.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/config/configuration.ts.j2 +85 -0
- package/templates/kits/nestjs-standard/src/config/index.ts.j2 +2 -0
- package/templates/kits/nestjs-standard/src/config/validation.ts.j2 +21 -0
- package/templates/kits/nestjs-standard/src/examples/dto/create-note.dto.ts.j2 +11 -0
- package/templates/kits/nestjs-standard/src/examples/examples.controller.ts.j2 +24 -0
- package/templates/kits/nestjs-standard/src/examples/examples.module.ts.j2 +10 -0
- package/templates/kits/nestjs-standard/src/examples/examples.service.ts.j2 +33 -0
- package/templates/kits/nestjs-standard/src/main.ts.j2 +53 -0
- package/templates/kits/nestjs-standard/src/modules/index.ts.j2 +25 -0
- package/templates/kits/nestjs-standard/test/app.controller.spec.ts.j2 +24 -0
- package/templates/kits/nestjs-standard/test/app.e2e-spec.ts.j2 +60 -0
- package/templates/kits/nestjs-standard/test/examples.controller.spec.ts.j2 +28 -0
- package/templates/kits/nestjs-standard/test/jest-e2e.json.j2 +15 -0
- package/templates/kits/nestjs-standard/tsconfig.build.json.j2 +12 -0
- package/templates/kits/nestjs-standard/tsconfig.json.j2 +26 -0
- package/workspai.config.example.cjs +62 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# {{ project_name | replace('_', ' ') | title }}
|
|
2
|
+
|
|
3
|
+
A minimal FastAPI service generated with Workspai's **FastAPI Standard Kit**. Python-backed capabilities (configuration, logging, persistence, observability, authentication, etc.) are provided by RapidKit Core modules when you opt into the Python engine.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Bootstrap dependencies (creates .venv + installs Poetry-managed deps)
|
|
9
|
+
npx workspai init # use make init if you prefer a Make target
|
|
10
|
+
|
|
11
|
+
# Copy env templates and install hooks/tooling
|
|
12
|
+
./bootstrap.sh
|
|
13
|
+
|
|
14
|
+
# Run linting, typing, testing, and supply-chain audits
|
|
15
|
+
make lint
|
|
16
|
+
make typecheck
|
|
17
|
+
make test
|
|
18
|
+
make audit
|
|
19
|
+
|
|
20
|
+
# Start development server with hot reload
|
|
21
|
+
make dev
|
|
22
|
+
npx workspai dev # same as make dev but auto-detects the project
|
|
23
|
+
|
|
24
|
+
# Prefer project-local RapidKit Core helpers when you want it to resolve scripts automatically
|
|
25
|
+
npx workspai lint
|
|
26
|
+
npx workspai test
|
|
27
|
+
npx workspai start
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> Re-run `npx workspai init` (or `make init`) whenever dependencies change, or use `SKIP_INIT=1 make install` if you only need to refresh tooling/hooks without reinstalling packages.
|
|
31
|
+
|
|
32
|
+
> Lockfiles are generated automatically during scaffolding. Set `RAPIDKIT_SKIP_LOCKS=1` (or `RAPIDKIT_GENERATE_LOCKS=0`) before running `workspai create` if you need to opt out.
|
|
33
|
+
|
|
34
|
+
> Want the full project-local RapidKit Core catalog? Run `rapidkit --help` when RapidKit Core is installed, or use `npx workspai --help` for workspace-level commands.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Local development
|
|
39
|
+
|
|
40
|
+
- `npx workspai init` bootstraps dependencies via the project-aware CLI.
|
|
41
|
+
- `make init` is an optional alias for `npx workspai init` when you prefer Make targets.
|
|
42
|
+
- `make install` re-runs `npx workspai init` (unless you set `SKIP_INIT=1`) and refreshes developer tooling such as pre-commit hooks.
|
|
43
|
+
- `./bootstrap.sh` copies `.env.example` to `.env` (if missing) and runs `SKIP_INIT=1 make install` for you.
|
|
44
|
+
- `make dev` (or `npx workspai dev`) launches Uvicorn with the correct module path and reload settings.
|
|
45
|
+
- `make lint`, `make typecheck`, and `make test` wrap Ruff, mypy, and pytest for consistent CI parity.
|
|
46
|
+
- `make audit` shells out to `pip-audit` to surface vulnerable dependencies.
|
|
47
|
+
- Prefer `npx workspai lint`, `npx workspai test`, and `npx workspai start` if you want the project-aware launcher to autodetect the virtualenv and command wiring.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Available commands
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx workspai init # 🔧 Bootstrap the project (create .venv + install deps)
|
|
54
|
+
make init # 🔧 Optional alias for npx workspai init
|
|
55
|
+
./bootstrap.sh # 🪄 Copy env template + install hooks/tooling (idempotent)
|
|
56
|
+
npx workspai dev # 🚀 Start development server with hot reload
|
|
57
|
+
make dev # 🚀 Alternative via Makefile helper
|
|
58
|
+
npx workspai start # ⚡ Start production server
|
|
59
|
+
npx workspai lint # 🔧 Run lint checks via project-aware CLI
|
|
60
|
+
npx workspai test # 🧪 Run pytest through the project-aware launcher
|
|
61
|
+
make install # 📦 Install Poetry deps + hooks
|
|
62
|
+
make lint # ✅ Lint via Ruff + Black
|
|
63
|
+
make typecheck # 🔍 Run mypy on src
|
|
64
|
+
make test # 🧪 Run tests
|
|
65
|
+
make audit # 🔐 Run pip-audit across dependencies
|
|
66
|
+
make docker-up # 🐳 Start docker compose stack (if enabled)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Project layout
|
|
70
|
+
|
|
71
|
+
- `src/main.py` – FastAPI application entrypoint
|
|
72
|
+
- `src/routing/` – Core routers (health) and anchors for module routers
|
|
73
|
+
- `src/modules/` – Module bootstrap anchors
|
|
74
|
+
- `pyproject.toml` – Poetry configuration and dependencies
|
|
75
|
+
- `Makefile` – Common developer workflows (format, lint, docker, etc.)
|
|
76
|
+
- `Dockerfile` / `docker-compose.yml` – Optional container setup for local dev and deployment
|
|
77
|
+
- `.github/workflows/ci.yml` – Optional GitHub Actions pipeline for linting and tests
|
|
78
|
+
|
|
79
|
+
## Example feature
|
|
80
|
+
|
|
81
|
+
The scaffold ships with a tiny in-memory **notes** API mounted under `/api/examples/notes`. Use it as a safe playground for wiring routers, schemas, and tests without touching your real domain logic:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Create a note
|
|
85
|
+
curl -s -X POST http://localhost:8000/api/examples/notes \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-d '{"title":"first","body":"scaffolded by Workspai"}'
|
|
88
|
+
|
|
89
|
+
# List notes
|
|
90
|
+
curl -s http://localhost:8000/api/examples/notes | jq
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The implementation intentionally stays in memory so you can replace it with a repository-backed service once you install RapidKit Core database modules.
|
|
94
|
+
|
|
95
|
+
{% if selected_modules %}
|
|
96
|
+
## Recommended RapidKit Core modules
|
|
97
|
+
|
|
98
|
+
The following RapidKit Core modules are suggested during scaffolding; install them any time with `rapidkit add module <name>` after activating the project launcher:
|
|
99
|
+
|
|
100
|
+
{% for module in selected_modules %}- {{ module.name | replace('_', ' ') | title }} (`{{ module.name }}`{% if not module.required %}, optional{% endif %}) – tier: {{ module.tier }}
|
|
101
|
+
{% endfor %}
|
|
102
|
+
{% endif %}
|
|
103
|
+
|
|
104
|
+
## Adding features
|
|
105
|
+
|
|
106
|
+
Use `rapidkit add module <module-name>` to install optional capabilities. Modules inject imports, routes, and services through the anchors defined in `src/main.py` and `src/routing/__init__.py`.
|
|
107
|
+
|
|
108
|
+
During kit generation you can decide whether the RapidKit Core modules ship with the scaffold:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
Install the RapidKit Core settings module? [Y/n]
|
|
112
|
+
Install the RapidKit Core logging module? [Y/n]
|
|
113
|
+
Install deployment module assets (Docker/CI)? [Y/n]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Scaffold toggles vs RapidKit Core modules
|
|
117
|
+
|
|
118
|
+
- `enable_*` prompts (for Docker, CI, SQLite, etc.) control the starter assets generated by this kit.
|
|
119
|
+
- `install_*` prompts control which RapidKit Core modules are installed up front.
|
|
120
|
+
- You can always add modules later with `rapidkit add module <name>` if you skip them during scaffolding.
|
|
121
|
+
|
|
122
|
+
When you need deployment artefacts in an existing project, install the optional `deployment` module:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
rapidkit add module deployment
|
|
126
|
+
rapidkit modules lock --overwrite
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
<!-- <<<inject:module-snippet>>> -->
|
|
130
|
+
|
|
131
|
+
## 📄 License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the **{{ license }}** License - see the `LICENSE` file included at the project root for details.
|
|
134
|
+
|
|
135
|
+
## 🔒 Security & secrets
|
|
136
|
+
|
|
137
|
+
- Copy `.env.example` to `.env` and populate secrets (`SECRET_KEY`, DB credentials, etc.) before deploying.
|
|
138
|
+
- **Do not** commit real secrets or `.env` to git; `.gitignore` already contains `.env` to protect accidental commits.
|
|
139
|
+
- For production, tighten CORS and allowed hosts rather than using wildcard settings provided for convenience in dev.
|
|
140
|
+
- Run `make audit` (pip-audit) before releases to catch dependency CVEs early.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% include 'common/env.example.j2' %}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "{{ project_name }}"
|
|
3
|
+
version = "{{ app_version }}"
|
|
4
|
+
description = "{{ description }}"
|
|
5
|
+
authors = ["{{ author }} <you@example.com>"]
|
|
6
|
+
license = "{{ license }}"
|
|
7
|
+
packages = [{ include = "src" }]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.scripts]
|
|
10
|
+
dev = "src.cli:dev"
|
|
11
|
+
start = "src.cli:start"
|
|
12
|
+
build = "src.cli:build"
|
|
13
|
+
test = "src.cli:test"
|
|
14
|
+
lint = "src.cli:lint"
|
|
15
|
+
format = "src.cli:format"
|
|
16
|
+
help = "src.cli:help_cmd"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.dependencies]
|
|
19
|
+
python = "^{{ python_version }}"
|
|
20
|
+
fastapi = "^0.128.0"
|
|
21
|
+
uvicorn = { extras = ["standard"], version = "^0.40.0" }
|
|
22
|
+
pydantic = "^2.12.5"
|
|
23
|
+
{% if has_settings -%}
|
|
24
|
+
pydantic-settings = { version = "^2.12.0", extras = ["yaml"] }
|
|
25
|
+
{% endif %}
|
|
26
|
+
# <<<inject:poetry-dependencies>>>
|
|
27
|
+
|
|
28
|
+
[tool.poetry.group.dev.dependencies]
|
|
29
|
+
pytest = "^9.0.2"
|
|
30
|
+
pytest-asyncio = "^1.3.0"
|
|
31
|
+
pytest-cov = "^7.0.0"
|
|
32
|
+
httpx = "^0.28.1"
|
|
33
|
+
ruff = "^0.14.10"
|
|
34
|
+
black = "^25.12.0"
|
|
35
|
+
isort = "^7.0.0"
|
|
36
|
+
mypy = "^1.19.1"
|
|
37
|
+
build = "^1.3.0"
|
|
38
|
+
|
|
39
|
+
# <<<inject:poetry-dev-dependencies>>>
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["poetry-core"]
|
|
43
|
+
build-backend = "poetry.core.masonry.api"
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
markers = [
|
|
47
|
+
"integration: mark tests as integration tests",
|
|
48
|
+
"template_integration: mark template integration tests",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
exclude = [".workspai/vendor", ".rapidkit/vendor"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
ignore = ["E501"]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "{{ python_version }}"
|
|
60
|
+
ignore_missing_imports = true
|
|
61
|
+
warn_unused_configs = false
|
|
62
|
+
show_error_codes = true
|
|
63
|
+
# Ignore vendor snapshots and generated health shims from strict static checking
|
|
64
|
+
exclude = "(^\\.workspai/vendor/|^\\.rapidkit/vendor/|src/health/|src/main.py$)"
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Project CLI wrapper for {{ project_name }}.
|
|
3
|
+
|
|
4
|
+
This file prefers a per-project CLI (generated at `.workspai/cli.py`) when
|
|
5
|
+
available. If not present it prints helpful guidance directing users to
|
|
6
|
+
`workspai init` and does not attempt to make assumptions about a packaging
|
|
7
|
+
environment.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import importlib.util
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _missing_cli(name: str) -> None:
|
|
18
|
+
print(f"❌ Missing project-local CLI (.workspai/cli.py) - can't run '{name}'")
|
|
19
|
+
print("💡 If you're a new user run: workspai init # create venv + install deps")
|
|
20
|
+
sys.exit(127)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load_project_cli_module() -> Optional[object]:
|
|
24
|
+
project_root = Path(__file__).resolve().parents[1]
|
|
25
|
+
cli_file = project_root / ".workspai" / "cli.py"
|
|
26
|
+
if not cli_file.exists():
|
|
27
|
+
legacy_cli_file = project_root / ".rapidkit" / "cli.py"
|
|
28
|
+
if not legacy_cli_file.exists():
|
|
29
|
+
return None
|
|
30
|
+
cli_file = legacy_cli_file
|
|
31
|
+
spec = importlib.util.spec_from_file_location("project_cli", str(cli_file))
|
|
32
|
+
if spec is None or spec.loader is None:
|
|
33
|
+
return None
|
|
34
|
+
module = importlib.util.module_from_spec(spec)
|
|
35
|
+
spec.loader.exec_module(module) # type: ignore
|
|
36
|
+
return module
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_mod = _load_project_cli_module()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Default placeholders -- call `_missing_cli` if the project-local CLI isn't
|
|
43
|
+
# available. These are real functions so linters don't complain.
|
|
44
|
+
def init() -> None:
|
|
45
|
+
_missing_cli("init")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dev() -> None:
|
|
49
|
+
_missing_cli("dev")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def start() -> None:
|
|
53
|
+
_missing_cli("start")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build() -> None:
|
|
57
|
+
_missing_cli("build")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test() -> None:
|
|
61
|
+
_missing_cli("test")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def lint() -> None:
|
|
65
|
+
_missing_cli("lint")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format() -> None: # type: ignore[misc]
|
|
69
|
+
_missing_cli("format")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def help_cmd() -> None:
|
|
73
|
+
print("Usage: poetry run <command> # (init|dev|start|build|test|lint|format)")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if _mod is not None:
|
|
77
|
+
# prefer project-provided implementations when available
|
|
78
|
+
init = getattr(_mod, "init", init)
|
|
79
|
+
dev = getattr(_mod, "dev", dev)
|
|
80
|
+
start = getattr(_mod, "start", start)
|
|
81
|
+
build = getattr(_mod, "build", build)
|
|
82
|
+
test = getattr(_mod, "test", test)
|
|
83
|
+
lint = getattr(_mod, "lint", lint)
|
|
84
|
+
format = getattr(_mod, "format", format)
|
|
85
|
+
help_cmd = getattr(_mod, "help_cmd", help_cmd)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_dev_impl = dev
|
|
89
|
+
_start_impl = start
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _runtime_kwargs_from_argv(argv: list[str]) -> dict:
|
|
93
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
94
|
+
parser.add_argument("-p", "--port", type=int, dest="port")
|
|
95
|
+
parser.add_argument("--host", dest="host")
|
|
96
|
+
parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
|
|
97
|
+
ns, _ = parser.parse_known_args(argv)
|
|
98
|
+
|
|
99
|
+
kwargs = {}
|
|
100
|
+
if ns.port is not None:
|
|
101
|
+
kwargs["port"] = ns.port
|
|
102
|
+
if ns.host is not None:
|
|
103
|
+
kwargs["host"] = ns.host
|
|
104
|
+
if ns.allow_global_runtime:
|
|
105
|
+
kwargs["allow_global_runtime"] = True
|
|
106
|
+
return kwargs
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def dev() -> None: # type: ignore[no-redef]
|
|
110
|
+
kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
|
|
111
|
+
try:
|
|
112
|
+
_dev_impl(**kwargs)
|
|
113
|
+
except TypeError:
|
|
114
|
+
_dev_impl()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def start() -> None: # type: ignore[no-redef]
|
|
118
|
+
kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
|
|
119
|
+
try:
|
|
120
|
+
_start_impl(**kwargs)
|
|
121
|
+
except TypeError:
|
|
122
|
+
_start_impl()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main() -> None:
|
|
126
|
+
if len(sys.argv) < 2:
|
|
127
|
+
help_cmd()
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
command = sys.argv[1]
|
|
131
|
+
commands = {
|
|
132
|
+
"init": init,
|
|
133
|
+
"dev": dev,
|
|
134
|
+
"start": start,
|
|
135
|
+
"build": build,
|
|
136
|
+
"test": test,
|
|
137
|
+
"lint": lint,
|
|
138
|
+
"format": format,
|
|
139
|
+
"help": help_cmd,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if command in commands:
|
|
143
|
+
if command in ("dev", "start"):
|
|
144
|
+
parser = argparse.ArgumentParser(prog=f"poetry run {command}", add_help=False)
|
|
145
|
+
parser.add_argument("-p", "--port", type=int, dest="port")
|
|
146
|
+
parser.add_argument("--host", dest="host")
|
|
147
|
+
parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
|
|
148
|
+
ns, _ = parser.parse_known_args(sys.argv[2:])
|
|
149
|
+
|
|
150
|
+
kwargs = {}
|
|
151
|
+
if ns.port is not None:
|
|
152
|
+
kwargs["port"] = ns.port
|
|
153
|
+
if ns.host is not None:
|
|
154
|
+
kwargs["host"] = ns.host
|
|
155
|
+
if ns.allow_global_runtime:
|
|
156
|
+
kwargs["allow_global_runtime"] = True
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
commands[command](**kwargs)
|
|
160
|
+
except TypeError:
|
|
161
|
+
commands[command]()
|
|
162
|
+
else:
|
|
163
|
+
commands[command]()
|
|
164
|
+
else:
|
|
165
|
+
print(f"Unknown command: {command}")
|
|
166
|
+
print("Run 'poetry run help' to see available commands.")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""{{ project_name }} application entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
10
|
+
|
|
11
|
+
# <<<inject:imports>>>
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
# canonical health package
|
|
15
|
+
from src.health import register_health_routes as _core_register_health_routes
|
|
16
|
+
except ImportError: # pragma: no cover - health package not generated yet
|
|
17
|
+
|
|
18
|
+
def _register_health_routes(_: FastAPI) -> None:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
|
|
23
|
+
def _register_health_routes(app: FastAPI) -> None:
|
|
24
|
+
try:
|
|
25
|
+
_core_register_health_routes(app)
|
|
26
|
+
except Exception: # pragma: no cover - defensive best-effort registration
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
from .routing import api_router
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@asynccontextmanager
|
|
34
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
35
|
+
"""Coordinate startup/shutdown hooks contributed by RapidKit Core modules."""
|
|
36
|
+
|
|
37
|
+
_ = app # ensure the app reference stays available for injected hooks
|
|
38
|
+
# <<<inject:startup>>>
|
|
39
|
+
try:
|
|
40
|
+
yield
|
|
41
|
+
finally:
|
|
42
|
+
# <<<inject:shutdown>>>
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app = FastAPI(
|
|
47
|
+
title="{{ project_name }}",
|
|
48
|
+
description="{{ description }}",
|
|
49
|
+
version="{{ app_version }}",
|
|
50
|
+
docs_url="/docs",
|
|
51
|
+
redoc_url="/redoc",
|
|
52
|
+
lifespan=lifespan,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
app.add_middleware(
|
|
56
|
+
CORSMiddleware,
|
|
57
|
+
allow_origins=["*"],
|
|
58
|
+
allow_credentials=True,
|
|
59
|
+
allow_methods=["*"],
|
|
60
|
+
allow_headers=["*"],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
app.include_router(api_router, prefix="/api")
|
|
64
|
+
# Expose module-provided health routers under /api/health/module/*
|
|
65
|
+
_register_health_routes(app)
|
|
66
|
+
# <<<inject:routes>>>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""API router assembly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
from .health import router as health_router
|
|
8
|
+
from .examples import router as examples_router
|
|
9
|
+
|
|
10
|
+
# <<<inject:router-imports>>>
|
|
11
|
+
|
|
12
|
+
api_router = APIRouter()
|
|
13
|
+
|
|
14
|
+
api_router.include_router(health_router, prefix="/health", tags=["health"])
|
|
15
|
+
api_router.include_router(examples_router, prefix="/examples", tags=["examples"])
|
|
16
|
+
# <<<inject:router-mount>>>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Sample in-memory notes endpoints to demonstrate Workspai routing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, HTTPException, status
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
router = APIRouter(tags=["examples"])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NoteCreate(BaseModel):
|
|
14
|
+
"""Payload for creating a note."""
|
|
15
|
+
|
|
16
|
+
title: str = Field(..., max_length=80)
|
|
17
|
+
body: str = Field(..., max_length=500)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Note(NoteCreate):
|
|
21
|
+
"""Serialized note returned by the API."""
|
|
22
|
+
|
|
23
|
+
id: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_STORE: Dict[int, Note] = {}
|
|
27
|
+
_NEXT_ID = 1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@router.post(
|
|
31
|
+
"/notes",
|
|
32
|
+
response_model=Note,
|
|
33
|
+
status_code=status.HTTP_201_CREATED,
|
|
34
|
+
summary="Create a note",
|
|
35
|
+
)
|
|
36
|
+
def create_note(payload: NoteCreate) -> Note:
|
|
37
|
+
"""Persist a note in memory and return it."""
|
|
38
|
+
|
|
39
|
+
global _NEXT_ID # noqa: PLW0603 - intentional in-memory auto increment
|
|
40
|
+
note = Note(id=_NEXT_ID, **payload.model_dump())
|
|
41
|
+
_STORE[_NEXT_ID] = note
|
|
42
|
+
_NEXT_ID += 1
|
|
43
|
+
return note
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.get(
|
|
47
|
+
"/notes",
|
|
48
|
+
response_model=List[Note],
|
|
49
|
+
summary="List all notes",
|
|
50
|
+
)
|
|
51
|
+
def list_notes() -> List[Note]:
|
|
52
|
+
"""Return all notes stored so far."""
|
|
53
|
+
|
|
54
|
+
return list(_STORE.values())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.get(
|
|
58
|
+
"/notes/{note_id}",
|
|
59
|
+
response_model=Note,
|
|
60
|
+
summary="Retrieve a single note",
|
|
61
|
+
)
|
|
62
|
+
def get_note(note_id: int) -> Note:
|
|
63
|
+
"""Retrieve a note by identifier or raise 404."""
|
|
64
|
+
|
|
65
|
+
note = _STORE.get(note_id)
|
|
66
|
+
if not note:
|
|
67
|
+
raise HTTPException(
|
|
68
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
69
|
+
detail="Note not found",
|
|
70
|
+
)
|
|
71
|
+
return note
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Health endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from time import monotonic
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
|
|
9
|
+
router = APIRouter(tags=["health"])
|
|
10
|
+
|
|
11
|
+
_START_TIME = monotonic()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("/", summary="Health check")
|
|
15
|
+
async def heartbeat() -> dict[str, object]:
|
|
16
|
+
"""Return basic service heartbeat."""
|
|
17
|
+
return {
|
|
18
|
+
"status": "ok",
|
|
19
|
+
"version": "{{ app_version }}",
|
|
20
|
+
"uptime": monotonic() - _START_TIME,
|
|
21
|
+
"module": "app",
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test package placeholder."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Example feature tests exercising the in-memory notes API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi.testclient import TestClient
|
|
6
|
+
|
|
7
|
+
from src.main import app
|
|
8
|
+
|
|
9
|
+
client = TestClient(app)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_create_and_list_notes() -> None:
|
|
13
|
+
payload = {"title": "First note", "body": "Documented via Workspai."}
|
|
14
|
+
create_response = client.post("/api/examples/notes", json=payload)
|
|
15
|
+
assert create_response.status_code == 201
|
|
16
|
+
data = create_response.json()
|
|
17
|
+
assert data["title"] == payload["title"]
|
|
18
|
+
assert data["body"] == payload["body"]
|
|
19
|
+
|
|
20
|
+
list_response = client.get("/api/examples/notes")
|
|
21
|
+
assert list_response.status_code == 200
|
|
22
|
+
notes = list_response.json()
|
|
23
|
+
assert any(note["title"] == payload["title"] for note in notes)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_missing_note_returns_404() -> None:
|
|
27
|
+
response = client.get("/api/examples/notes/999")
|
|
28
|
+
assert response.status_code == 404
|
|
29
|
+
assert response.json()["detail"] == "Note not found"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Health-check tests for generated FastAPI standard projects."""
|
|
2
|
+
|
|
3
|
+
from fastapi.testclient import TestClient
|
|
4
|
+
|
|
5
|
+
from src.main import app
|
|
6
|
+
|
|
7
|
+
client = TestClient(app)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_health_endpoint() -> None:
|
|
11
|
+
response = client.get("/api/health/")
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
payload = response.json()
|
|
14
|
+
assert payload["status"] == "ok"
|
|
15
|
+
assert "version" in payload
|
|
16
|
+
assert "uptime" in payload
|
|
17
|
+
assert "module" in payload
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.6
|
|
2
|
+
ARG NODE_VERSION=20.19.0-alpine
|
|
3
|
+
FROM node:${NODE_VERSION} as builder
|
|
4
|
+
|
|
5
|
+
WORKDIR /usr/src/app
|
|
6
|
+
|
|
7
|
+
COPY package*.json ./
|
|
8
|
+
COPY . .
|
|
9
|
+
|
|
10
|
+
RUN set -eux; \
|
|
11
|
+
if [ "{{ package_manager }}" = "pnpm" ]; then \
|
|
12
|
+
corepack enable pnpm && pnpm install --frozen-lockfile; \
|
|
13
|
+
pnpm build; \
|
|
14
|
+
elif [ "{{ package_manager }}" = "yarn" ]; then \
|
|
15
|
+
corepack enable yarn && yarn install --frozen-lockfile; \
|
|
16
|
+
yarn build; \
|
|
17
|
+
else \
|
|
18
|
+
if [ -f package-lock.json ]; then npm ci; else npm install; fi; \
|
|
19
|
+
npm run build; \
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
FROM node:${NODE_VERSION}
|
|
23
|
+
ENV NODE_ENV=production
|
|
24
|
+
WORKDIR /usr/src/app
|
|
25
|
+
|
|
26
|
+
COPY package*.json ./
|
|
27
|
+
|
|
28
|
+
RUN set -eux; \
|
|
29
|
+
if [ "{{ package_manager }}" = "pnpm" ]; then \
|
|
30
|
+
corepack enable pnpm && pnpm install --prod --frozen-lockfile; \
|
|
31
|
+
elif [ "{{ package_manager }}" = "yarn" ]; then \
|
|
32
|
+
corepack enable yarn && yarn install --production --frozen-lockfile; \
|
|
33
|
+
else \
|
|
34
|
+
if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi; \
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
COPY --from=builder /usr/src/app/dist ./dist
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
EXPOSE 8000
|
|
41
|
+
CMD ["node", "dist/main"]
|