@atlashub/smartstack-cli 1.37.0 → 2.0.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/config/mcp-defaults.json +62 -0
- package/dist/index.js +57 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +16984 -0
- package/dist/mcp-entry.mjs.map +1 -0
- package/package.json +14 -5
- package/templates/agents/gitflow/start.md +5 -4
- package/templates/agents/mcp-healthcheck.md +15 -13
- package/templates/mcp-scaffolding/component.tsx.hbs +298 -0
- package/templates/mcp-scaffolding/controller.cs.hbs +184 -0
- package/templates/mcp-scaffolding/entity-extension.cs.hbs +231 -0
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +116 -0
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +134 -0
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +261 -0
- package/templates/mcp-scaffolding/service-extension.cs.hbs +53 -0
- package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +413 -0
- package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +239 -0
- package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +441 -0
- package/templates/mcp-scaffolding/tests/security.test.cs.hbs +442 -0
- package/templates/mcp-scaffolding/tests/service.test.cs.hbs +390 -0
- package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +428 -0
- package/templates/ralph/README.md +3 -3
- package/templates/ralph/ralph.config.yaml +2 -2
- package/templates/skills/admin/SKILL.md +42 -0
- package/templates/skills/business-analyse/_shared.md +24 -1
- package/templates/skills/business-analyse/questionnaire/01-context.md +4 -4
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +3 -3
- package/templates/skills/business-analyse/questionnaire/03-scope.md +4 -4
- package/templates/skills/business-analyse/questionnaire/04-data.md +7 -7
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +1 -1
- package/templates/skills/business-analyse/questionnaire/06-security.md +3 -3
- package/templates/skills/business-analyse/questionnaire/07-ui.md +1 -1
- package/templates/skills/business-analyse/questionnaire/08-performance.md +3 -3
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +4 -4
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +2 -2
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +2 -2
- package/templates/skills/business-analyse/questionnaire/12-migration.md +1 -1
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +2 -2
- package/templates/skills/business-analyse/steps/step-01-discover.md +50 -25
- package/templates/skills/business-analyse/steps/step-05-handoff.md +133 -34
- package/templates/skills/cc-agent/SKILL.md +129 -0
- package/templates/skills/cc-agent/references/agent-frontmatter.md +213 -0
- package/templates/skills/cc-agent/references/permission-modes.md +102 -0
- package/templates/skills/cc-agent/references/tools-reference.md +144 -0
- package/templates/skills/cc-agent/steps/step-00-init.md +134 -0
- package/templates/skills/cc-agent/steps/step-01-design.md +186 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +204 -0
- package/templates/skills/cc-agent/steps/step-03-validate.md +130 -0
- package/templates/skills/cc-agent/templates/agent-categorized.md +67 -0
- package/templates/skills/cc-agent/templates/agent-standalone.md +56 -0
- package/templates/skills/cc-agent/templates/agent-with-skills.md +94 -0
- package/templates/skills/cc-audit/SKILL.md +108 -0
- package/templates/skills/cc-audit/references/agent-checklist.md +91 -0
- package/templates/skills/cc-audit/references/hook-checklist.md +110 -0
- package/templates/skills/cc-audit/references/skill-checklist.md +70 -0
- package/templates/skills/cc-audit/steps/step-00-init.md +98 -0
- package/templates/skills/cc-audit/steps/step-01-scan.md +142 -0
- package/templates/skills/cc-audit/steps/step-02-analyze.md +158 -0
- package/templates/skills/cc-audit/steps/step-03-report.md +142 -0
- package/templates/skills/cc-skill/SKILL.md +134 -0
- package/templates/skills/cc-skill/references/best-practices.md +167 -0
- package/templates/skills/cc-skill/references/frontmatter-reference.md +182 -0
- package/templates/skills/cc-skill/references/skill-patterns.md +199 -0
- package/templates/skills/cc-skill/steps/step-00-init.md +119 -0
- package/templates/skills/cc-skill/steps/step-01-design.md +199 -0
- package/templates/skills/cc-skill/steps/step-02-generate.md +145 -0
- package/templates/skills/cc-skill/steps/step-03-steps.md +151 -0
- package/templates/skills/cc-skill/steps/step-04-validate.md +124 -0
- package/templates/skills/cc-skill/templates/skill-forked.md +85 -0
- package/templates/skills/cc-skill/templates/skill-progressive.md +102 -0
- package/templates/skills/cc-skill/templates/skill-simple.md +75 -0
- package/templates/skills/cc-skill/templates/step-template.md +82 -0
- package/templates/skills/check-version/SKILL.md +6 -0
- package/templates/skills/debug/SKILL.md +4 -0
- package/templates/skills/documentation/SKILL.md +1 -0
- package/templates/skills/efcore/SKILL.md +5 -0
- package/templates/skills/efcore/steps/db/step-deploy.md +26 -5
- package/templates/skills/efcore/steps/shared/step-00-init.md +21 -7
- package/templates/skills/explore/SKILL.md +28 -32
- package/templates/skills/feature-full/SKILL.md +1 -0
- package/templates/skills/gitflow/SKILL.md +8 -0
- package/templates/skills/gitflow/steps/step-start.md +45 -10
- package/templates/skills/mcp/SKILL.md +38 -18
- package/templates/skills/quick-search/SKILL.md +8 -1
- package/templates/skills/ralph-loop/SKILL.md +1 -1
- package/templates/skills/ralph-loop/steps/step-00-init.md +8 -68
- package/templates/skills/ralph-loop/steps/step-04-check.md +1 -1
- package/templates/skills/refactor/SKILL.md +1 -0
- package/templates/skills/review-code/SKILL.md +7 -1
- package/templates/skills/ui-components/SKILL.md +31 -438
- package/templates/skills/ui-components/accessibility.md +170 -0
- package/templates/skills/ui-components/patterns/data-table.md +39 -0
- package/templates/skills/ui-components/patterns/entity-card.md +77 -0
- package/templates/skills/ui-components/patterns/grid-layout.md +91 -0
- package/templates/skills/ui-components/patterns/kanban.md +43 -0
- package/templates/skills/ui-components/style-guide.md +86 -0
- package/templates/skills/utils/SKILL.md +1 -0
- package/templates/skills/validate/SKILL.md +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlashub/smartstack-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "SmartStack Claude Code automation toolkit - GitFlow, APEX, EF Core migrations, prompts and more",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SmartStack",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"bin": {
|
|
16
16
|
"smartstack": "./dist/index.js",
|
|
17
|
-
"ss": "./dist/index.js"
|
|
17
|
+
"ss": "./dist/index.js",
|
|
18
|
+
"smartstack-mcp": "./dist/mcp-entry.mjs"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|
|
@@ -69,7 +70,10 @@
|
|
|
69
70
|
"ralph:start": "node dist/index.js ralph start",
|
|
70
71
|
"ralph:status": "node dist/index.js ralph status",
|
|
71
72
|
"ralph:logs": "node dist/index.js ralph logs",
|
|
72
|
-
"health": "node dist/index.js doctor --json"
|
|
73
|
+
"health": "node dist/index.js doctor --json",
|
|
74
|
+
"test:mcp": "vitest run --config vitest.mcp.config.ts",
|
|
75
|
+
"test:mcp:watch": "vitest --config vitest.mcp.config.ts",
|
|
76
|
+
"test:mcp:coverage": "vitest run --config vitest.mcp.config.ts --coverage"
|
|
73
77
|
},
|
|
74
78
|
"dependencies": {
|
|
75
79
|
"bcryptjs": "^2.4.3",
|
|
@@ -82,7 +86,10 @@
|
|
|
82
86
|
"jsonwebtoken": "^9.0.3",
|
|
83
87
|
"mssql": "^11.0.1",
|
|
84
88
|
"ora": "^8.0.1",
|
|
85
|
-
"zod": "^3.
|
|
89
|
+
"zod": "^3.25.0",
|
|
90
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
91
|
+
"handlebars": "^4.7.8",
|
|
92
|
+
"axios": "^1.7.0"
|
|
86
93
|
},
|
|
87
94
|
"devDependencies": {
|
|
88
95
|
"@types/bcryptjs": "^2.4.6",
|
|
@@ -96,6 +103,8 @@
|
|
|
96
103
|
"eslint": "^8.56.0",
|
|
97
104
|
"prettier": "^3.2.4",
|
|
98
105
|
"tsup": "^8.0.1",
|
|
99
|
-
"typescript": "^5.3.3"
|
|
106
|
+
"typescript": "^5.3.3",
|
|
107
|
+
"vitest": "^2.1.0",
|
|
108
|
+
"@vitest/coverage-v8": "^2.1.0"
|
|
100
109
|
}
|
|
101
110
|
}
|
|
@@ -15,9 +15,10 @@ Creation rapide de branche GitFlow avec worktree.
|
|
|
15
15
|
1. **Analyser**: Contexte (branche, version, ahead/behind)
|
|
16
16
|
2. **Choisir**: Type (feature/release/hotfix)
|
|
17
17
|
3. **Creer**: Worktree ou checkout
|
|
18
|
-
4. **Configurer**: appsettings.Local.json si .NET
|
|
19
|
-
5. **
|
|
20
|
-
6. **
|
|
18
|
+
4. **Configurer**: appsettings.Local.json + launchSettings.json Local profile si .NET
|
|
19
|
+
5. **Configurer**: .env.local + npm run local si Web
|
|
20
|
+
6. **Verifier ports**: Detecter conflits entre projets
|
|
21
|
+
7. **Resume**: Prochaines etapes
|
|
21
22
|
|
|
22
23
|
## Commandes
|
|
23
24
|
|
|
@@ -48,7 +49,7 @@ BRANCH CREATED
|
|
|
48
49
|
Name: {branch}
|
|
49
50
|
Base: {develop|main}
|
|
50
51
|
Worktree: ../worktrees/{type}s/{name}
|
|
51
|
-
Config: {appsettings.Local.json
|
|
52
|
+
Config: {appsettings.Local.json + launchSettings.json Local|skipped}
|
|
52
53
|
Ports: {✓ OK|⚠️ Conflicts resolved|⚠️ Conflicts ignored}
|
|
53
54
|
|
|
54
55
|
NEXT STEPS:
|
|
@@ -10,6 +10,8 @@ tools: Bash, Read, Write, Glob
|
|
|
10
10
|
|
|
11
11
|
Validates that SmartStack MCP is properly configured and up-to-date before any operation.
|
|
12
12
|
|
|
13
|
+
> **Note:** The MCP server is now bundled inside `@atlashub/smartstack-cli`. No separate package required.
|
|
14
|
+
|
|
13
15
|
## Status File
|
|
14
16
|
|
|
15
17
|
Location: `.claude/mcp-status.json`
|
|
@@ -17,8 +19,8 @@ Location: `.claude/mcp-status.json`
|
|
|
17
19
|
```json
|
|
18
20
|
{
|
|
19
21
|
"lastCheck": "2024-01-15T10:30:00Z",
|
|
20
|
-
"mcpVersion": "
|
|
21
|
-
"
|
|
22
|
+
"mcpVersion": "2.0.0",
|
|
23
|
+
"bundled": true,
|
|
22
24
|
"status": "ok",
|
|
23
25
|
"tools": ["validate_conventions", "scaffold_extension", "check_migrations", "..."]
|
|
24
26
|
}
|
|
@@ -55,7 +57,7 @@ If fails → Run full check
|
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
59
|
# Check Claude Code MCP config
|
|
58
|
-
|
|
60
|
+
claude mcp list 2>/dev/null | grep -i smartstack
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
#### 3.2 Test MCP Tools Availability
|
|
@@ -71,15 +73,15 @@ Call each critical tool to verify availability:
|
|
|
71
73
|
#### 3.3 Check MCP Version
|
|
72
74
|
|
|
73
75
|
```bash
|
|
74
|
-
# Read MCP
|
|
75
|
-
|
|
76
|
+
# Read version from CLI package (MCP is bundled)
|
|
77
|
+
smartstack --version 2>/dev/null || npx @atlashub/smartstack-cli --version
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
#### 3.4 Compare with Latest Release
|
|
79
81
|
|
|
80
82
|
```bash
|
|
81
83
|
# Check npm registry for latest version
|
|
82
|
-
npm view @
|
|
84
|
+
npm view @atlashub/smartstack-cli version 2>/dev/null || echo "Registry check failed"
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
### 4. Update Status File
|
|
@@ -88,7 +90,7 @@ npm view @anthropic/smartstack-mcp version 2>/dev/null || echo "Not published"
|
|
|
88
90
|
Write(".claude/mcp-status.json", {
|
|
89
91
|
lastCheck: new Date().toISOString(),
|
|
90
92
|
mcpVersion: "<detected_version>",
|
|
91
|
-
|
|
93
|
+
bundled: true,
|
|
92
94
|
status: "ok" | "error",
|
|
93
95
|
tools: ["<available_tools>"],
|
|
94
96
|
errors: ["<any_errors>"] // if status = error
|
|
@@ -101,18 +103,18 @@ Write(".claude/mcp-status.json", {
|
|
|
101
103
|
```
|
|
102
104
|
MCP HEALTH CHECK
|
|
103
105
|
Status: ✓ OK
|
|
104
|
-
Version:
|
|
106
|
+
Version: 2.0.0 (bundled in CLI)
|
|
105
107
|
Last check: 2024-01-15
|
|
106
|
-
Tools available:
|
|
108
|
+
Tools available: 19/19
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
### Warning (outdated)
|
|
110
112
|
```
|
|
111
113
|
MCP HEALTH CHECK
|
|
112
114
|
Status: ⚠️ UPDATE AVAILABLE
|
|
113
|
-
Current: 1.
|
|
114
|
-
Latest:
|
|
115
|
-
Action:
|
|
115
|
+
Current: 1.38.0
|
|
116
|
+
Latest: 2.0.0
|
|
117
|
+
Action: npm update -g @atlashub/smartstack-cli
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
### Error
|
|
@@ -120,7 +122,7 @@ MCP HEALTH CHECK
|
|
|
120
122
|
MCP HEALTH CHECK
|
|
121
123
|
Status: ❌ ERROR
|
|
122
124
|
Issue: MCP server not responding
|
|
123
|
-
Action:
|
|
125
|
+
Action: claude mcp add smartstack -- npx -p @atlashub/smartstack-cli smartstack-mcp
|
|
124
126
|
```
|
|
125
127
|
|
|
126
128
|
## Integration
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Types
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export interface {{name}}Data {
|
|
8
|
+
id?: string;
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
updatedAt?: string;
|
|
11
|
+
// TODO: Add {{name}} specific properties
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface {{name}}Props {
|
|
15
|
+
/** Entity ID for edit mode */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** Initial data */
|
|
18
|
+
initialData?: Partial<{{name}}Data>;
|
|
19
|
+
/** Callback when data is saved */
|
|
20
|
+
onSave?: (data: {{name}}Data) => void;
|
|
21
|
+
/** Callback when cancelled */
|
|
22
|
+
onCancel?: () => void;
|
|
23
|
+
/** Loading state from parent */
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
/** Read-only mode */
|
|
26
|
+
readOnly?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Component
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* {{name}} component
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <{{name}}
|
|
39
|
+
* id="123"
|
|
40
|
+
* onSave={(data) => console.log('Saved:', data)}
|
|
41
|
+
* onCancel={() => navigate(-1)}
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const {{name}}: React.FC<{{name}}Props> = ({
|
|
46
|
+
id,
|
|
47
|
+
initialData,
|
|
48
|
+
onSave,
|
|
49
|
+
onCancel,
|
|
50
|
+
loading: externalLoading,
|
|
51
|
+
readOnly = false,
|
|
52
|
+
}) => {
|
|
53
|
+
// State
|
|
54
|
+
const [data, setData] = useState<{{name}}Data>(initialData || {});
|
|
55
|
+
const [loading, setLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState<string | null>(null);
|
|
57
|
+
const [isDirty, setIsDirty] = useState(false);
|
|
58
|
+
|
|
59
|
+
// Combined loading state
|
|
60
|
+
const isLoading = loading || externalLoading;
|
|
61
|
+
|
|
62
|
+
// Fetch data when ID changes
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (id && !initialData) {
|
|
65
|
+
fetchData(id);
|
|
66
|
+
}
|
|
67
|
+
}, [id, initialData]);
|
|
68
|
+
|
|
69
|
+
// Fetch data from API
|
|
70
|
+
const fetchData = useCallback(async (fetchId: string) => {
|
|
71
|
+
setLoading(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// TODO: Implement API call
|
|
76
|
+
// const response = await {{nameCamel}}Api.getById(fetchId);
|
|
77
|
+
// setData(response);
|
|
78
|
+
|
|
79
|
+
// Placeholder
|
|
80
|
+
setData({ id: fetchId });
|
|
81
|
+
} catch (e) {
|
|
82
|
+
setError(e instanceof Error ? e.message : 'Failed to load data');
|
|
83
|
+
} finally {
|
|
84
|
+
setLoading(false);
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
// Handle field change
|
|
89
|
+
const handleChange = useCallback((field: keyof {{name}}Data, value: unknown) => {
|
|
90
|
+
setData(prev => ({ ...prev, [field]: value }));
|
|
91
|
+
setIsDirty(true);
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
// Handle form submission
|
|
95
|
+
const handleSubmit = useCallback(async (e: React.FormEvent) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
|
|
98
|
+
if (readOnly) return;
|
|
99
|
+
|
|
100
|
+
setLoading(true);
|
|
101
|
+
setError(null);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// TODO: Implement API call
|
|
105
|
+
// const result = data.id
|
|
106
|
+
// ? await {{nameCamel}}Api.update(data.id, data)
|
|
107
|
+
// : await {{nameCamel}}Api.create(data);
|
|
108
|
+
|
|
109
|
+
if (onSave) {
|
|
110
|
+
onSave(data);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setIsDirty(false);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
setError(e instanceof Error ? e.message : 'Failed to save');
|
|
116
|
+
} finally {
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
}, [data, onSave, readOnly]);
|
|
120
|
+
|
|
121
|
+
// Handle cancel
|
|
122
|
+
const handleCancel = useCallback(() => {
|
|
123
|
+
if (isDirty) {
|
|
124
|
+
const confirmed = window.confirm('You have unsaved changes. Are you sure you want to cancel?');
|
|
125
|
+
if (!confirmed) return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (onCancel) {
|
|
129
|
+
onCancel();
|
|
130
|
+
}
|
|
131
|
+
}, [isDirty, onCancel]);
|
|
132
|
+
|
|
133
|
+
// Render loading state
|
|
134
|
+
if (isLoading && !data.id) {
|
|
135
|
+
return (
|
|
136
|
+
<div className="flex items-center justify-center p-8">
|
|
137
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
|
|
138
|
+
<span className="ml-2 text-gray-600">Loading...</span>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Render error state
|
|
144
|
+
if (error) {
|
|
145
|
+
return (
|
|
146
|
+
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
147
|
+
<div className="flex items-center">
|
|
148
|
+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
|
149
|
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
|
150
|
+
</svg>
|
|
151
|
+
<span className="ml-2 text-red-700">{error}</span>
|
|
152
|
+
</div>
|
|
153
|
+
<button
|
|
154
|
+
onClick={() => id && fetchData(id)}
|
|
155
|
+
className="mt-2 text-sm text-red-600 hover:text-red-800 underline"
|
|
156
|
+
>
|
|
157
|
+
Try again
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|
165
|
+
{/* Header */}
|
|
166
|
+
<div className="px-6 py-4 border-b border-gray-200">
|
|
167
|
+
<h2 className="text-xl font-semibold text-gray-900">
|
|
168
|
+
{id ? 'Edit' : 'Create'} {{name}}
|
|
169
|
+
</h2>
|
|
170
|
+
{isDirty && (
|
|
171
|
+
<span className="text-sm text-amber-600">Unsaved changes</span>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Form */}
|
|
176
|
+
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
|
177
|
+
{/* TODO: Add form fields */}
|
|
178
|
+
<div className="text-gray-500 text-center py-8">
|
|
179
|
+
Add your form fields here
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Actions */}
|
|
183
|
+
{!readOnly && (
|
|
184
|
+
<div className="flex items-center justify-end gap-3 pt-4 border-t border-gray-200">
|
|
185
|
+
{onCancel && (
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={handleCancel}
|
|
189
|
+
disabled={isLoading}
|
|
190
|
+
className="px-4 py-2 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50"
|
|
191
|
+
>
|
|
192
|
+
Cancel
|
|
193
|
+
</button>
|
|
194
|
+
)}
|
|
195
|
+
<button
|
|
196
|
+
type="submit"
|
|
197
|
+
disabled={isLoading || !isDirty}
|
|
198
|
+
className="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
199
|
+
>
|
|
200
|
+
{isLoading ? 'Saving...' : 'Save'}
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</form>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export default {{name}};
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Hook
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
export interface Use{{name}}Options {
|
|
216
|
+
id?: string;
|
|
217
|
+
autoFetch?: boolean;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function use{{name}}(options: Use{{name}}Options = {}) {
|
|
221
|
+
const { id, autoFetch = true } = options;
|
|
222
|
+
|
|
223
|
+
const [data, setData] = useState<{{name}}Data | null>(null);
|
|
224
|
+
const [loading, setLoading] = useState(false);
|
|
225
|
+
const [error, setError] = useState<Error | null>(null);
|
|
226
|
+
|
|
227
|
+
const fetch = useCallback(async (fetchId?: string) => {
|
|
228
|
+
const targetId = fetchId || id;
|
|
229
|
+
if (!targetId) return;
|
|
230
|
+
|
|
231
|
+
setLoading(true);
|
|
232
|
+
setError(null);
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// TODO: Implement API call
|
|
236
|
+
// const result = await {{nameCamel}}Api.getById(targetId);
|
|
237
|
+
// setData(result);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
setError(e instanceof Error ? e : new Error('Unknown error'));
|
|
240
|
+
} finally {
|
|
241
|
+
setLoading(false);
|
|
242
|
+
}
|
|
243
|
+
}, [id]);
|
|
244
|
+
|
|
245
|
+
const save = useCallback(async (saveData: {{name}}Data) => {
|
|
246
|
+
setLoading(true);
|
|
247
|
+
setError(null);
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// TODO: Implement API call
|
|
251
|
+
// const result = saveData.id
|
|
252
|
+
// ? await {{nameCamel}}Api.update(saveData.id, saveData)
|
|
253
|
+
// : await {{nameCamel}}Api.create(saveData);
|
|
254
|
+
// setData(result);
|
|
255
|
+
// return result;
|
|
256
|
+
} catch (e) {
|
|
257
|
+
setError(e instanceof Error ? e : new Error('Unknown error'));
|
|
258
|
+
throw e;
|
|
259
|
+
} finally {
|
|
260
|
+
setLoading(false);
|
|
261
|
+
}
|
|
262
|
+
}, []);
|
|
263
|
+
|
|
264
|
+
const remove = useCallback(async (removeId?: string) => {
|
|
265
|
+
const targetId = removeId || id;
|
|
266
|
+
if (!targetId) return;
|
|
267
|
+
|
|
268
|
+
setLoading(true);
|
|
269
|
+
setError(null);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// TODO: Implement API call
|
|
273
|
+
// await {{nameCamel}}Api.delete(targetId);
|
|
274
|
+
setData(null);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
setError(e instanceof Error ? e : new Error('Unknown error'));
|
|
277
|
+
throw e;
|
|
278
|
+
} finally {
|
|
279
|
+
setLoading(false);
|
|
280
|
+
}
|
|
281
|
+
}, [id]);
|
|
282
|
+
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (autoFetch && id) {
|
|
285
|
+
fetch();
|
|
286
|
+
}
|
|
287
|
+
}, [autoFetch, id, fetch]);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
data,
|
|
291
|
+
loading,
|
|
292
|
+
error,
|
|
293
|
+
fetch,
|
|
294
|
+
save,
|
|
295
|
+
remove,
|
|
296
|
+
setData,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Threading;
|
|
4
|
+
using System.Threading.Tasks;
|
|
5
|
+
using Microsoft.AspNetCore.Authorization;
|
|
6
|
+
using Microsoft.AspNetCore.Mvc;
|
|
7
|
+
using Microsoft.Extensions.Logging;
|
|
8
|
+
|
|
9
|
+
namespace {{namespace}}.Controllers;
|
|
10
|
+
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// API controller for {{name}} operations
|
|
13
|
+
/// </summary>
|
|
14
|
+
[ApiController]
|
|
15
|
+
[Route("api/[controller]")]
|
|
16
|
+
{{#if navRoute}}
|
|
17
|
+
[NavRoute("{{navRoute}}")]
|
|
18
|
+
{{/if}}
|
|
19
|
+
[Authorize]
|
|
20
|
+
[Produces("application/json")]
|
|
21
|
+
public class {{name}}Controller : ControllerBase
|
|
22
|
+
{
|
|
23
|
+
private readonly ILogger<{{name}}Controller> _logger;
|
|
24
|
+
// private readonly I{{name}}Service _{{nameCamel}}Service;
|
|
25
|
+
|
|
26
|
+
public {{name}}Controller(
|
|
27
|
+
ILogger<{{name}}Controller> logger
|
|
28
|
+
// I{{name}}Service {{nameCamel}}Service
|
|
29
|
+
)
|
|
30
|
+
{
|
|
31
|
+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
32
|
+
// _{{nameCamel}}Service = {{nameCamel}}Service ?? throw new ArgumentNullException(nameof({{nameCamel}}Service));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// <summary>
|
|
36
|
+
/// Get all {{namePlural}}
|
|
37
|
+
/// </summary>
|
|
38
|
+
/// <param name="cancellationToken">Cancellation token</param>
|
|
39
|
+
/// <returns>List of {{namePlural}}</returns>
|
|
40
|
+
[HttpGet]
|
|
41
|
+
{{#if navRoute}}
|
|
42
|
+
[Authorize(Policy = "{{navRoute}}.read")]
|
|
43
|
+
{{/if}}
|
|
44
|
+
[ProducesResponseType(typeof(IEnumerable<{{name}}Dto>), 200)]
|
|
45
|
+
public async Task<ActionResult<IEnumerable<{{name}}Dto>>> GetAll(CancellationToken cancellationToken = default)
|
|
46
|
+
{
|
|
47
|
+
_logger.LogInformation("Getting all {{namePlural}}");
|
|
48
|
+
|
|
49
|
+
// TODO: Implement using service
|
|
50
|
+
// var result = await _{{nameCamel}}Service.GetAllAsync(cancellationToken);
|
|
51
|
+
// return Ok(result);
|
|
52
|
+
|
|
53
|
+
return Ok(Array.Empty<{{name}}Dto>());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Get {{nameCamel}} by ID
|
|
58
|
+
/// </summary>
|
|
59
|
+
/// <param name="id">{{name}} ID</param>
|
|
60
|
+
/// <param name="cancellationToken">Cancellation token</param>
|
|
61
|
+
/// <returns>{{name}} details</returns>
|
|
62
|
+
[HttpGet("{id:guid}")]
|
|
63
|
+
{{#if navRoute}}
|
|
64
|
+
[Authorize(Policy = "{{navRoute}}.read")]
|
|
65
|
+
{{/if}}
|
|
66
|
+
[ProducesResponseType(typeof({{name}}Dto), 200)]
|
|
67
|
+
[ProducesResponseType(404)]
|
|
68
|
+
public async Task<ActionResult<{{name}}Dto>> GetById(
|
|
69
|
+
Guid id,
|
|
70
|
+
CancellationToken cancellationToken = default)
|
|
71
|
+
{
|
|
72
|
+
_logger.LogInformation("Getting {{nameCamel}} {Id}", id);
|
|
73
|
+
|
|
74
|
+
// TODO: Implement using service
|
|
75
|
+
// var result = await _{{nameCamel}}Service.GetByIdAsync(id, cancellationToken);
|
|
76
|
+
// if (result == null) return NotFound();
|
|
77
|
+
// return Ok(result);
|
|
78
|
+
|
|
79
|
+
return NotFound();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// <summary>
|
|
83
|
+
/// Create new {{nameCamel}}
|
|
84
|
+
/// </summary>
|
|
85
|
+
/// <param name="request">Create request</param>
|
|
86
|
+
/// <param name="cancellationToken">Cancellation token</param>
|
|
87
|
+
/// <returns>Created {{nameCamel}}</returns>
|
|
88
|
+
[HttpPost]
|
|
89
|
+
{{#if navRoute}}
|
|
90
|
+
[Authorize(Policy = "{{navRoute}}.create")]
|
|
91
|
+
{{/if}}
|
|
92
|
+
[ProducesResponseType(typeof({{name}}Dto), 201)]
|
|
93
|
+
[ProducesResponseType(400)]
|
|
94
|
+
public async Task<ActionResult<{{name}}Dto>> Create(
|
|
95
|
+
[FromBody] Create{{name}}Request request,
|
|
96
|
+
CancellationToken cancellationToken = default)
|
|
97
|
+
{
|
|
98
|
+
_logger.LogInformation("Creating new {{nameCamel}}");
|
|
99
|
+
|
|
100
|
+
// TODO: Validate and create using service
|
|
101
|
+
// var result = await _{{nameCamel}}Service.CreateAsync(request, cancellationToken);
|
|
102
|
+
// return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
103
|
+
|
|
104
|
+
var id = Guid.NewGuid();
|
|
105
|
+
return CreatedAtAction(nameof(GetById), new { id }, null);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// <summary>
|
|
109
|
+
/// Update {{nameCamel}}
|
|
110
|
+
/// </summary>
|
|
111
|
+
/// <param name="id">{{name}} ID</param>
|
|
112
|
+
/// <param name="request">Update request</param>
|
|
113
|
+
/// <param name="cancellationToken">Cancellation token</param>
|
|
114
|
+
[HttpPut("{id:guid}")]
|
|
115
|
+
{{#if navRoute}}
|
|
116
|
+
[Authorize(Policy = "{{navRoute}}.update")]
|
|
117
|
+
{{/if}}
|
|
118
|
+
[ProducesResponseType(204)]
|
|
119
|
+
[ProducesResponseType(404)]
|
|
120
|
+
[ProducesResponseType(400)]
|
|
121
|
+
public async Task<ActionResult> Update(
|
|
122
|
+
Guid id,
|
|
123
|
+
[FromBody] Update{{name}}Request request,
|
|
124
|
+
CancellationToken cancellationToken = default)
|
|
125
|
+
{
|
|
126
|
+
_logger.LogInformation("Updating {{nameCamel}} {Id}", id);
|
|
127
|
+
|
|
128
|
+
// TODO: Implement using service
|
|
129
|
+
// await _{{nameCamel}}Service.UpdateAsync(id, request, cancellationToken);
|
|
130
|
+
|
|
131
|
+
return NoContent();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// <summary>
|
|
135
|
+
/// Delete {{nameCamel}}
|
|
136
|
+
/// </summary>
|
|
137
|
+
/// <param name="id">{{name}} ID</param>
|
|
138
|
+
/// <param name="cancellationToken">Cancellation token</param>
|
|
139
|
+
[HttpDelete("{id:guid}")]
|
|
140
|
+
{{#if navRoute}}
|
|
141
|
+
[Authorize(Policy = "{{navRoute}}.delete")]
|
|
142
|
+
{{/if}}
|
|
143
|
+
[ProducesResponseType(204)]
|
|
144
|
+
[ProducesResponseType(404)]
|
|
145
|
+
public async Task<ActionResult> Delete(
|
|
146
|
+
Guid id,
|
|
147
|
+
CancellationToken cancellationToken = default)
|
|
148
|
+
{
|
|
149
|
+
_logger.LogInformation("Deleting {{nameCamel}} {Id}", id);
|
|
150
|
+
|
|
151
|
+
// TODO: Implement using service
|
|
152
|
+
// await _{{nameCamel}}Service.DeleteAsync(id, cancellationToken);
|
|
153
|
+
|
|
154
|
+
return NoContent();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// DTOs
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
/// <summary>
|
|
163
|
+
/// {{name}} data transfer object
|
|
164
|
+
/// </summary>
|
|
165
|
+
public record {{name}}Dto(
|
|
166
|
+
Guid Id,
|
|
167
|
+
DateTime CreatedAt,
|
|
168
|
+
DateTime? UpdatedAt
|
|
169
|
+
// TODO: Add additional properties
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
/// <summary>
|
|
173
|
+
/// Request to create a new {{nameCamel}}
|
|
174
|
+
/// </summary>
|
|
175
|
+
public record Create{{name}}Request(
|
|
176
|
+
// TODO: Add creation properties
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
/// <summary>
|
|
180
|
+
/// Request to update a {{nameCamel}}
|
|
181
|
+
/// </summary>
|
|
182
|
+
public record Update{{name}}Request(
|
|
183
|
+
// TODO: Add update properties
|
|
184
|
+
);
|