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,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Workspai Project Generator
|
|
4
|
+
* This script is called by the Workspai CLI to generate projects
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const TEMPLATES_DIR = path.join(__dirname, 'templates');
|
|
12
|
+
|
|
13
|
+
async function generateProject(projectPath, template, useDefaults, skipGit, skipInstall) {
|
|
14
|
+
const projectName = path.basename(projectPath);
|
|
15
|
+
const templateDir = template === 'nestjs' ? 'nestjs-standard' : 'fastapi-standard';
|
|
16
|
+
const templatePath = path.join(TEMPLATES_DIR, templateDir);
|
|
17
|
+
|
|
18
|
+
// Check template exists
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(templatePath);
|
|
21
|
+
} catch {
|
|
22
|
+
console.error('❌ Template not found:', templateDir);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create project directory
|
|
27
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
28
|
+
|
|
29
|
+
// Build context
|
|
30
|
+
const context = {
|
|
31
|
+
project_name: template === 'nestjs'
|
|
32
|
+
? projectName.replace(/_/g, '-').toLowerCase()
|
|
33
|
+
: projectName.replace(/-/g, '_').toLowerCase(),
|
|
34
|
+
author: process.env.USER || 'Workspai User',
|
|
35
|
+
description: template === 'nestjs'
|
|
36
|
+
? 'NestJS application generated with Workspai'
|
|
37
|
+
: 'FastAPI service generated with Workspai',
|
|
38
|
+
app_version: '0.1.0',
|
|
39
|
+
license: 'MIT',
|
|
40
|
+
package_manager: 'npm',
|
|
41
|
+
created_at: new Date().toISOString(),
|
|
42
|
+
rapidkit_version: require('./config.json').rapidkit_version || '0.12.0',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Copy and render template files
|
|
46
|
+
await copyTemplateDir(templatePath, projectPath, context);
|
|
47
|
+
|
|
48
|
+
console.log('✅ Project files created!');
|
|
49
|
+
|
|
50
|
+
// Git initialization
|
|
51
|
+
if (!skipGit) {
|
|
52
|
+
try {
|
|
53
|
+
execSync('git init', { cwd: projectPath, stdio: 'pipe' });
|
|
54
|
+
execSync('git add .', { cwd: projectPath, stdio: 'pipe' });
|
|
55
|
+
const commitMsg = 'Initial commit: ' + (template === 'nestjs' ? 'NestJS' : 'FastAPI') + ' project via Workspai';
|
|
56
|
+
execSync(`git commit -m "${commitMsg}"`, { cwd: projectPath, stdio: 'pipe' });
|
|
57
|
+
console.log('✅ Git repository initialized');
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.log('⚠️ Could not initialize git');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Install dependencies
|
|
64
|
+
if (!skipInstall) {
|
|
65
|
+
if (template === 'nestjs') {
|
|
66
|
+
console.log('📦 Installing dependencies...');
|
|
67
|
+
try {
|
|
68
|
+
execSync('npm install', { cwd: projectPath, stdio: 'inherit' });
|
|
69
|
+
console.log('✅ Dependencies installed');
|
|
70
|
+
} catch {
|
|
71
|
+
console.log('⚠️ Could not install dependencies. Run npm install manually.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Success message
|
|
77
|
+
const templateName = template === 'nestjs' ? 'NestJS' : 'FastAPI';
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log('✨ ' + templateName + ' project created successfully!');
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log('🚀 Get started:');
|
|
82
|
+
console.log(' cd ' + projectName);
|
|
83
|
+
if (template === 'fastapi') {
|
|
84
|
+
console.log(' workspai init # poetry install');
|
|
85
|
+
console.log(' workspai dev # Start dev server');
|
|
86
|
+
} else {
|
|
87
|
+
if (skipInstall) {
|
|
88
|
+
console.log(' workspai init # npm install');
|
|
89
|
+
}
|
|
90
|
+
console.log(' cp .env.example .env');
|
|
91
|
+
console.log(' workspai dev # Start dev server');
|
|
92
|
+
}
|
|
93
|
+
console.log('');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderTemplate(content, context) {
|
|
97
|
+
let result = content;
|
|
98
|
+
|
|
99
|
+
for (const [key, value] of Object.entries(context)) {
|
|
100
|
+
// Simple variable replacement: {{ key }}
|
|
101
|
+
const simpleRegex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
|
|
102
|
+
result = result.replace(simpleRegex, String(value));
|
|
103
|
+
|
|
104
|
+
// With replace filter: {{ key | replace('a', 'b') }}
|
|
105
|
+
const replaceRegex = new RegExp(
|
|
106
|
+
`\\{\\{\\s*${key}\\s*\\|\\s*replace\\s*\\(\\s*['"]([^'"]+)['"]\\s*,\\s*['"]([^'"]*)['"]\\s*\\)\\s*\\}\\}`,
|
|
107
|
+
'g'
|
|
108
|
+
);
|
|
109
|
+
result = result.replace(replaceRegex, (match, from, to) => {
|
|
110
|
+
return String(value).replace(new RegExp(from, 'g'), to);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// With lower filter: {{ key | lower }}
|
|
114
|
+
const lowerRegex = new RegExp(`\\{\\{\\s*${key}\\s*\\|\\s*lower\\s*\\}\\}`, 'g');
|
|
115
|
+
result = result.replace(lowerRegex, String(value).toLowerCase());
|
|
116
|
+
|
|
117
|
+
// Combined: {{ key | replace('a', 'b') | lower }}
|
|
118
|
+
const combinedRegex = new RegExp(
|
|
119
|
+
`\\{\\{\\s*${key}\\s*\\|\\s*replace\\s*\\(\\s*['"]([^'"]+)['"]\\s*,\\s*['"]([^'"]*)['"]\\s*\\)\\s*\\|\\s*lower\\s*\\}\\}`,
|
|
120
|
+
'g'
|
|
121
|
+
);
|
|
122
|
+
result = result.replace(combinedRegex, (match, from, to) => {
|
|
123
|
+
return String(value).replace(new RegExp(from, 'g'), to).toLowerCase();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function copyTemplateDir(src, dest, context) {
|
|
131
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
132
|
+
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const srcPath = path.join(src, entry.name);
|
|
135
|
+
const destName = entry.name.replace(/\.j2$/, '');
|
|
136
|
+
const destPath = path.join(dest, destName);
|
|
137
|
+
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
140
|
+
await copyTemplateDir(srcPath, destPath, context);
|
|
141
|
+
} else {
|
|
142
|
+
let content = await fs.readFile(srcPath, 'utf-8');
|
|
143
|
+
|
|
144
|
+
// Render template if it's a .j2 file
|
|
145
|
+
if (entry.name.endsWith('.j2')) {
|
|
146
|
+
content = renderTemplate(content, context);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await fs.writeFile(destPath, content);
|
|
150
|
+
|
|
151
|
+
// Make scripts executable
|
|
152
|
+
if (
|
|
153
|
+
destName === 'rapidkit' ||
|
|
154
|
+
(destName.endsWith('.py') &&
|
|
155
|
+
(destPath.includes('.workspai') || destPath.includes('.rapidkit')))
|
|
156
|
+
) {
|
|
157
|
+
await fs.chmod(destPath, 0o755);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Main
|
|
164
|
+
const args = process.argv.slice(2);
|
|
165
|
+
const projectPath = args[0];
|
|
166
|
+
const template = args[1] || 'fastapi';
|
|
167
|
+
const useDefaults = args.includes('--yes');
|
|
168
|
+
const skipGit = args.includes('--skip-git');
|
|
169
|
+
const skipInstall = args.includes('--skip-install');
|
|
170
|
+
|
|
171
|
+
if (!projectPath) {
|
|
172
|
+
console.error('Usage: node generator.js <project-path> <template> [--yes] [--skip-git] [--skip-install]');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
generateProject(projectPath, template, useDefaults, skipGit, skipInstall).catch(err => {
|
|
177
|
+
console.error('Error:', err);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# {{ project_name }}
|
|
2
|
+
|
|
3
|
+
Domain-driven FastAPI service generated with Workspai. Python-backed module commands use [RapidKit Core](https://github.com/rapidkitlabs/rapidkit-core).
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Bootstrap dependencies (creates .venv + installs Poetry 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
|
+
# Type-check, lint, and test
|
|
15
|
+
make lint
|
|
16
|
+
make typecheck
|
|
17
|
+
make test
|
|
18
|
+
|
|
19
|
+
# Run the service
|
|
20
|
+
make dev
|
|
21
|
+
|
|
22
|
+
# Use the project-local RapidKit Core helpers when you prefer auto-detected environments
|
|
23
|
+
npx workspai dev
|
|
24
|
+
npx workspai lint
|
|
25
|
+
npx workspai test
|
|
26
|
+
|
|
27
|
+
# Run supply-chain and dependency audits
|
|
28
|
+
make audit
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> Re-run `npx workspai init` (or `make init`) when dependencies change. If you only need to refresh tooling/hooks, run `SKIP_INIT=1 make install` or rely on `./bootstrap.sh`.
|
|
32
|
+
|
|
33
|
+
> Lockfiles are generated automatically during scaffolding. Set `RAPIDKIT_SKIP_LOCKS=1` (or `RAPIDKIT_GENERATE_LOCKS=0`) before running `workspai create` if you want to skip the lockfile attempt.
|
|
34
|
+
|
|
35
|
+
> Need to see every project-local RapidKit Core command? Run `rapidkit --help` when RapidKit Core is installed, or use `npx workspai --help` for workspace-level commands.
|
|
36
|
+
|
|
37
|
+
## Project structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
src/
|
|
41
|
+
app/
|
|
42
|
+
application/ # Use cases and service contracts
|
|
43
|
+
domain/ # Aggregates, entities, and value objects
|
|
44
|
+
infrastructure/ # Adapters and repositories
|
|
45
|
+
presentation/ # API routers and dependencies
|
|
46
|
+
shared/ # Cross-cutting primitives (Result, types, etc.)
|
|
47
|
+
routing/ # Glue for module-provided routers
|
|
48
|
+
modules/ # RapidKit Core module bootstrapping
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Layer overview
|
|
52
|
+
|
|
53
|
+
- **Domain** – Pure business rules (`src/app/domain`).
|
|
54
|
+
- **Application** – Orchestrates use-cases and enforces boundaries (`src/app/application`).
|
|
55
|
+
- **Infrastructure** – Implements gateways and repositories (`src/app/infrastructure`).
|
|
56
|
+
- **Presentation** – Exposes the API using FastAPI (`src/app/presentation`).
|
|
57
|
+
|
|
58
|
+
Modules added via `rapidkit add module ...` integrate automatically using the preserved
|
|
59
|
+
injection anchors inside `src/main.py` and `src/routing/__init__.py`.
|
|
60
|
+
|
|
61
|
+
## Example feature
|
|
62
|
+
|
|
63
|
+
An in-memory notes module is included under `/api/examples/notes` to demonstrate how the domain, application, infrastructure, and presentation layers collaborate:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Create a note
|
|
67
|
+
curl -s -X POST http://localhost:8000/api/examples/notes \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-d '{"title":"design","body":"Describe your bounded context here."}'
|
|
70
|
+
|
|
71
|
+
# Retrieve the newly created note
|
|
72
|
+
curl -s http://localhost:8000/api/examples/notes/1 | jq
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Swap the in-memory repository with a RapidKit Core database module when you are ready for persistence.
|
|
76
|
+
|
|
77
|
+
## Deployment
|
|
78
|
+
|
|
79
|
+
- `Dockerfile` packages the service for container hosting with production-ready defaults.
|
|
80
|
+
- `docker-compose.yml` provisions the FastAPI app plus dependencies (for example PostgreSQL) for local production parity.
|
|
81
|
+
- Run `docker compose up --build` to start the stack; copy `.env.example` to `.env` to configure credentials before deploying.
|
|
82
|
+
- CI/CD workflows under `.github/workflows/` demonstrate automated deployment pipelines.
|
|
83
|
+
|
|
84
|
+
## Testing
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx workspai test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The scaffold ships with a smoke test (`tests/test_app_factory.py`) that validates the
|
|
91
|
+
health endpoint and the application factory.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Local development
|
|
96
|
+
|
|
97
|
+
- `npx workspai init` bootstraps dependencies via the project-aware CLI.
|
|
98
|
+
- `make init` is an optional alias for `npx workspai init` when you prefer Make targets.
|
|
99
|
+
- `make install` re-runs `npx workspai init` unless `SKIP_INIT=1` is set and refreshes developer tooling such as pre-commit hooks.
|
|
100
|
+
- `./bootstrap.sh` copies `.env.example` to `.env` (when missing) and runs `SKIP_INIT=1 make install` for you.
|
|
101
|
+
- `make dev` or `npx workspai dev` starts the FastAPI server with hot-reload and ensures environment variables from `.env` are loaded.
|
|
102
|
+
- `make lint`, `make typecheck`, and `make test` (or `npx workspai lint` / `npx workspai test`) are thin wrappers around Ruff, mypy, and pytest for consistent CI parity.
|
|
103
|
+
- `make audit` runs `pip-audit` and `npm audit` to surface vulnerable dependencies early.
|
|
104
|
+
|
|
105
|
+
Pre-commit is configured with Ruff, Black, mypy, and hygiene hooks; `./bootstrap.sh` installs them automatically (or run `SKIP_INIT=1 make install`). Use the project-local launcher variants when you need auto-discovery of the right virtual environment inside automation scripts.
|
|
106
|
+
|
|
107
|
+
<!-- <<<inject:module-snippet>>> -->
|
|
108
|
+
|
|
109
|
+
## 📄 License
|
|
110
|
+
|
|
111
|
+
This project is licensed under the **{{ license }}** License - see the `LICENSE` file included at the project root for details.
|
|
112
|
+
|
|
113
|
+
## 🔒 Security & secrets
|
|
114
|
+
|
|
115
|
+
- Copy `.env.example` to `.env` and populate secrets (`SECRET_KEY`, DB credentials, etc.) before deploying.
|
|
116
|
+
- **Do not** commit real secrets or `.env` to git; `.gitignore` already contains `.env` to protect accidental commits.
|
|
117
|
+
- For production, tighten CORS and allowed hosts rather than using wildcard settings provided for convenience in dev.
|
|
@@ -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$|src/app/)"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Contracts between the application layer and infrastructure adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from src.app.domain.models import HealthStatus, Note, NoteDraft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HealthStatusProvider(Protocol):
|
|
12
|
+
"""Protocol describing how to obtain the current health status."""
|
|
13
|
+
|
|
14
|
+
def get_status(self) -> HealthStatus:
|
|
15
|
+
"""Return the latest domain health status snapshot."""
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NoteRepository(Protocol):
|
|
20
|
+
"""Protocol for managing example notes."""
|
|
21
|
+
|
|
22
|
+
def create(self, draft: NoteDraft) -> Note:
|
|
23
|
+
"""Persist a note draft and return the resulting entity."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def list(self) -> list[Note]:
|
|
27
|
+
"""Return all available notes."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def get(self, note_id: int) -> Note | None:
|
|
31
|
+
"""Return a single note or None if it does not exist."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class ServiceContext:
|
|
37
|
+
"""Aggregates infrastructure adapters used by application use-cases."""
|
|
38
|
+
|
|
39
|
+
health_provider: HealthStatusProvider
|
|
40
|
+
note_repository: NoteRepository
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["HealthStatusProvider", "NoteRepository", "ServiceContext"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Example use-case exposing service health information."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from src.app.application.interfaces import ServiceContext
|
|
6
|
+
from src.app.domain.models import HealthStatus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_service_health(context: ServiceContext) -> dict[str, str]:
|
|
10
|
+
"""Retrieve an immutable health snapshot for presentation layers."""
|
|
11
|
+
status = context.health_provider.get_status()
|
|
12
|
+
if isinstance(status, HealthStatus):
|
|
13
|
+
return status.to_dict()
|
|
14
|
+
return {"status": str(status)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Application use-cases orchestrating the example notes feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from src.app.application.interfaces import ServiceContext
|
|
6
|
+
from src.app.domain.models import Note, NoteDraft
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_notes(context: ServiceContext) -> list[Note]:
|
|
10
|
+
"""Return every note currently stored."""
|
|
11
|
+
|
|
12
|
+
return context.note_repository.list()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_note(context: ServiceContext, draft: NoteDraft) -> Note:
|
|
16
|
+
"""Create a new note."""
|
|
17
|
+
|
|
18
|
+
return context.note_repository.create(draft)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_note(context: ServiceContext, note_id: int) -> Note | None:
|
|
22
|
+
"""Retrieve a single note by identifier."""
|
|
23
|
+
|
|
24
|
+
return context.note_repository.get(note_id)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Configuration primitives for the application layer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class AppConfig:
|
|
10
|
+
"""Application-level configuration aggregated from RapidKit Core settings."""
|
|
11
|
+
|
|
12
|
+
environment: str = "development"
|
|
13
|
+
service_name: str = "{{ project_name }}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["AppConfig"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Domain entity representing system health."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class HealthStatus:
|
|
10
|
+
"""Immutable representation of the service health state."""
|
|
11
|
+
|
|
12
|
+
status: str = "ok"
|
|
13
|
+
|
|
14
|
+
def to_dict(self) -> dict[str, str]:
|
|
15
|
+
"""Serialize the status for transport layers."""
|
|
16
|
+
return {"status": self.status}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Domain entity and value object for example notes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class NoteDraft:
|
|
10
|
+
"""Value object describing the data needed to create a note."""
|
|
11
|
+
|
|
12
|
+
title: str
|
|
13
|
+
body: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class Note:
|
|
18
|
+
"""Domain entity representing an immutable note."""
|
|
19
|
+
|
|
20
|
+
id: int
|
|
21
|
+
title: str
|
|
22
|
+
body: str
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
25
|
+
"""Serialize the note for presentation layers."""
|
|
26
|
+
|
|
27
|
+
return {"id": self.id, "title": self.title, "body": self.body}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Infrastructure repository providing health status data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from src.app.domain.models import HealthStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class StaticHealthRepository:
|
|
12
|
+
"""Simple repository returning a constant health status."""
|
|
13
|
+
|
|
14
|
+
status: str = "ok"
|
|
15
|
+
|
|
16
|
+
def get_status(self) -> HealthStatus:
|
|
17
|
+
return HealthStatus(status=self.status)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""In-memory repository backing the example notes use-case."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
from src.app.domain.models import Note, NoteDraft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(slots=True)
|
|
12
|
+
class InMemoryNoteRepository:
|
|
13
|
+
"""Very small repository storing notes in process memory."""
|
|
14
|
+
|
|
15
|
+
_store: Dict[int, Note] = field(default_factory=dict)
|
|
16
|
+
_next_id: int = 1
|
|
17
|
+
|
|
18
|
+
def create(self, draft: NoteDraft) -> Note:
|
|
19
|
+
note = Note(id=self._next_id, title=draft.title, body=draft.body)
|
|
20
|
+
self._store[self._next_id] = note
|
|
21
|
+
self._next_id += 1
|
|
22
|
+
return note
|
|
23
|
+
|
|
24
|
+
def list(self) -> List[Note]:
|
|
25
|
+
return list(self._store.values())
|
|
26
|
+
|
|
27
|
+
def get(self, note_id: int) -> Note | None:
|
|
28
|
+
return self._store.get(note_id)
|