@polymorphism-tech/morph-spec 1.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/README.md +279 -0
- package/bin/morph-spec.js +53 -0
- package/content/.claude/commands/morph-apply.md +66 -0
- package/content/.claude/commands/morph-archive.md +79 -0
- package/content/.claude/commands/morph-costs.md +206 -0
- package/content/.claude/commands/morph-infra.md +209 -0
- package/content/.claude/commands/morph-proposal.md +60 -0
- package/content/.claude/commands/morph-status.md +71 -0
- package/content/.claude/settings.local.json +15 -0
- package/content/.claude/skills/infra/bicep-architect.md +419 -0
- package/content/.claude/skills/infra/container-specialist.md +437 -0
- package/content/.claude/skills/infra/devops-engineer.md +405 -0
- package/content/.claude/skills/integrations/asaas-financial.md +333 -0
- package/content/.claude/skills/integrations/azure-identity.md +309 -0
- package/content/.claude/skills/integrations/clerk-auth.md +290 -0
- package/content/.claude/skills/specialists/azure-architect.md +142 -0
- package/content/.claude/skills/specialists/cost-guardian.md +110 -0
- package/content/.claude/skills/specialists/ef-modeler.md +200 -0
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +245 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +209 -0
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -0
- package/content/.claude/skills/specialists/standards-architect.md +78 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +325 -0
- package/content/.claude/skills/stacks/dotnet-blazor.md +352 -0
- package/content/.claude/skills/stacks/dotnet-nextjs.md +402 -0
- package/content/.claude/skills/stacks/shopify.md +445 -0
- package/content/.morph/archive/.gitkeep +25 -0
- package/content/.morph/config/agents.json +149 -0
- package/content/.morph/config/config.template.json +96 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -0
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -0
- package/content/.morph/examples/api-nextjs/spec.md +399 -0
- package/content/.morph/examples/api-nextjs/tasks.md +168 -0
- package/content/.morph/examples/micro-saas/README.md +125 -0
- package/content/.morph/examples/micro-saas/contracts.cs +358 -0
- package/content/.morph/examples/micro-saas/decisions.md +246 -0
- package/content/.morph/examples/micro-saas/spec.md +236 -0
- package/content/.morph/examples/micro-saas/tasks.md +150 -0
- package/content/.morph/examples/multi-agent/README.md +309 -0
- package/content/.morph/examples/multi-agent/contracts.cs +433 -0
- package/content/.morph/examples/multi-agent/spec.md +479 -0
- package/content/.morph/examples/multi-agent/tasks.md +185 -0
- package/content/.morph/features/.gitkeep +25 -0
- package/content/.morph/project.md +159 -0
- package/content/.morph/specs/.gitkeep +20 -0
- package/content/.morph/standards/architecture.md +190 -0
- package/content/.morph/standards/azure.md +184 -0
- package/content/.morph/standards/coding.md +342 -0
- package/content/.morph/templates/agent.cs +172 -0
- package/content/.morph/templates/component.razor +239 -0
- package/content/.morph/templates/contracts.cs +217 -0
- package/content/.morph/templates/decisions.md +106 -0
- package/content/.morph/templates/infra/app-insights.bicep +63 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -0
- package/content/.morph/templates/infra/container-app.bicep +156 -0
- package/content/.morph/templates/infra/key-vault.bicep +91 -0
- package/content/.morph/templates/infra/main.bicep +155 -0
- package/content/.morph/templates/infra/parameters.dev.json +23 -0
- package/content/.morph/templates/infra/parameters.prod.json +23 -0
- package/content/.morph/templates/infra/sql-database.bicep +103 -0
- package/content/.morph/templates/infra/storage.bicep +106 -0
- package/content/.morph/templates/integrations/asaas-client.cs +387 -0
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/content/.morph/templates/integrations/clerk-config.cs +258 -0
- package/content/.morph/templates/job.cs +171 -0
- package/content/.morph/templates/migration.cs +83 -0
- package/content/.morph/templates/proposal.md +155 -0
- package/content/.morph/templates/recap.md +105 -0
- package/content/.morph/templates/repository.cs +141 -0
- package/content/.morph/templates/saas/subscription.cs +347 -0
- package/content/.morph/templates/saas/tenant.cs +338 -0
- package/content/.morph/templates/service.cs +139 -0
- package/content/.morph/templates/spec.md +147 -0
- package/content/.morph/templates/tasks.md +235 -0
- package/content/.morph/templates/test.cs +239 -0
- package/content/CLAUDE.md +318 -0
- package/package.json +50 -0
- package/src/commands/doctor.js +132 -0
- package/src/commands/init.js +121 -0
- package/src/commands/update.js +84 -0
- package/src/utils/file-copier.js +50 -0
- package/src/utils/logger.js +32 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# .NET + Next.js Stack
|
|
2
|
+
|
|
3
|
+
Stack full-stack com .NET Web API no backend e Next.js no frontend.
|
|
4
|
+
|
|
5
|
+
## Visão Geral
|
|
6
|
+
|
|
7
|
+
| Aspecto | Tecnologia |
|
|
8
|
+
|---------|------------|
|
|
9
|
+
| **Backend** | .NET 9+ Web API |
|
|
10
|
+
| **Frontend** | Next.js 15+ / React 19 |
|
|
11
|
+
| **Database** | Entity Framework Core + Azure SQL |
|
|
12
|
+
| **API** | REST + OpenAPI |
|
|
13
|
+
| **Hosting** | Azure Container Apps |
|
|
14
|
+
|
|
15
|
+
## Triggers
|
|
16
|
+
|
|
17
|
+
Keywords: `nextjs`, `next.js`, `react`, `api`, `frontend`, `spa`, `typescript`
|
|
18
|
+
|
|
19
|
+
## Estrutura de Projeto
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/
|
|
23
|
+
├── backend/ # .NET Web API
|
|
24
|
+
│ ├── src/
|
|
25
|
+
│ │ ├── Api/
|
|
26
|
+
│ │ │ ├── Program.cs
|
|
27
|
+
│ │ │ ├── Controllers/
|
|
28
|
+
│ │ │ └── Endpoints/ # Minimal APIs
|
|
29
|
+
│ │ ├── Application/
|
|
30
|
+
│ │ │ ├── Services/
|
|
31
|
+
│ │ │ └── DTOs/
|
|
32
|
+
│ │ ├── Domain/
|
|
33
|
+
│ │ │ └── Entities/
|
|
34
|
+
│ │ └── Infrastructure/
|
|
35
|
+
│ │ └── Data/
|
|
36
|
+
│ ├── tests/
|
|
37
|
+
│ └── Backend.sln
|
|
38
|
+
│
|
|
39
|
+
├── frontend/ # Next.js
|
|
40
|
+
│ ├── src/
|
|
41
|
+
│ │ ├── app/ # App Router
|
|
42
|
+
│ │ │ ├── layout.tsx
|
|
43
|
+
│ │ │ ├── page.tsx
|
|
44
|
+
│ │ │ └── (routes)/
|
|
45
|
+
│ │ ├── components/
|
|
46
|
+
│ │ ├── lib/
|
|
47
|
+
│ │ │ ├── api.ts # API client
|
|
48
|
+
│ │ │ └── types.ts # Generated from OpenAPI
|
|
49
|
+
│ │ └── hooks/
|
|
50
|
+
│ ├── package.json
|
|
51
|
+
│ └── next.config.js
|
|
52
|
+
│
|
|
53
|
+
├── docker-compose.yml
|
|
54
|
+
└── infra/
|
|
55
|
+
└── main.bicep
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Backend (.NET API)
|
|
59
|
+
|
|
60
|
+
### Program.cs (Minimal API)
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
64
|
+
|
|
65
|
+
// OpenAPI
|
|
66
|
+
builder.Services.AddEndpointsApiExplorer();
|
|
67
|
+
builder.Services.AddSwaggerGen(c =>
|
|
68
|
+
{
|
|
69
|
+
c.SwaggerDoc("v1", new() { Title = "My API", Version = "v1" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// CORS para Next.js
|
|
73
|
+
builder.Services.AddCors(options =>
|
|
74
|
+
{
|
|
75
|
+
options.AddPolicy("Frontend", policy =>
|
|
76
|
+
{
|
|
77
|
+
policy.WithOrigins(builder.Configuration["Frontend:Url"]!)
|
|
78
|
+
.AllowAnyHeader()
|
|
79
|
+
.AllowAnyMethod()
|
|
80
|
+
.AllowCredentials();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Database
|
|
85
|
+
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
86
|
+
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
|
|
87
|
+
|
|
88
|
+
// Services
|
|
89
|
+
builder.Services.AddScoped<IOrderService, OrderService>();
|
|
90
|
+
|
|
91
|
+
var app = builder.Build();
|
|
92
|
+
|
|
93
|
+
// Swagger (dev only)
|
|
94
|
+
if (app.Environment.IsDevelopment())
|
|
95
|
+
{
|
|
96
|
+
app.UseSwagger();
|
|
97
|
+
app.UseSwaggerUI();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
app.UseHttpsRedirection();
|
|
101
|
+
app.UseCors("Frontend");
|
|
102
|
+
app.UseAuthentication();
|
|
103
|
+
app.UseAuthorization();
|
|
104
|
+
|
|
105
|
+
// Map endpoints
|
|
106
|
+
app.MapOrderEndpoints();
|
|
107
|
+
|
|
108
|
+
app.Run();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Minimal API Endpoints
|
|
112
|
+
|
|
113
|
+
```csharp
|
|
114
|
+
// Endpoints/OrderEndpoints.cs
|
|
115
|
+
public static class OrderEndpoints
|
|
116
|
+
{
|
|
117
|
+
public static void MapOrderEndpoints(this WebApplication app)
|
|
118
|
+
{
|
|
119
|
+
var group = app.MapGroup("/api/orders")
|
|
120
|
+
.WithTags("Orders")
|
|
121
|
+
.RequireAuthorization();
|
|
122
|
+
|
|
123
|
+
group.MapGet("/", GetAll);
|
|
124
|
+
group.MapGet("/{id:int}", GetById);
|
|
125
|
+
group.MapPost("/", Create);
|
|
126
|
+
group.MapPut("/{id:int}", Update);
|
|
127
|
+
group.MapDelete("/{id:int}", Delete);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static async Task<IResult> GetAll(
|
|
131
|
+
IOrderService service,
|
|
132
|
+
[AsParameters] PaginationQuery query)
|
|
133
|
+
{
|
|
134
|
+
var result = await service.GetAllAsync(query);
|
|
135
|
+
return Results.Ok(result);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static async Task<IResult> GetById(
|
|
139
|
+
int id,
|
|
140
|
+
IOrderService service)
|
|
141
|
+
{
|
|
142
|
+
var order = await service.GetByIdAsync(id);
|
|
143
|
+
return order is null ? Results.NotFound() : Results.Ok(order);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private static async Task<IResult> Create(
|
|
147
|
+
CreateOrderRequest request,
|
|
148
|
+
IOrderService service)
|
|
149
|
+
{
|
|
150
|
+
var order = await service.CreateAsync(request);
|
|
151
|
+
return Results.Created($"/api/orders/{order.Id}", order);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private static async Task<IResult> Update(
|
|
155
|
+
int id,
|
|
156
|
+
UpdateOrderRequest request,
|
|
157
|
+
IOrderService service)
|
|
158
|
+
{
|
|
159
|
+
await service.UpdateAsync(id, request);
|
|
160
|
+
return Results.NoContent();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private static async Task<IResult> Delete(
|
|
164
|
+
int id,
|
|
165
|
+
IOrderService service)
|
|
166
|
+
{
|
|
167
|
+
await service.DeleteAsync(id);
|
|
168
|
+
return Results.NoContent();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Frontend (Next.js)
|
|
174
|
+
|
|
175
|
+
### API Client
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// lib/api.ts
|
|
179
|
+
const API_URL = process.env.NEXT_PUBLIC_API_URL!;
|
|
180
|
+
|
|
181
|
+
async function fetchApi<T>(
|
|
182
|
+
endpoint: string,
|
|
183
|
+
options?: RequestInit
|
|
184
|
+
): Promise<T> {
|
|
185
|
+
const res = await fetch(`${API_URL}${endpoint}`, {
|
|
186
|
+
...options,
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'application/json',
|
|
189
|
+
...options?.headers,
|
|
190
|
+
},
|
|
191
|
+
credentials: 'include',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
throw new Error(`API error: ${res.status}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return res.json();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const api = {
|
|
202
|
+
orders: {
|
|
203
|
+
getAll: (params?: { page?: number; pageSize?: number }) =>
|
|
204
|
+
fetchApi<PaginatedResult<Order>>(`/api/orders?${new URLSearchParams(params as any)}`),
|
|
205
|
+
|
|
206
|
+
getById: (id: number) =>
|
|
207
|
+
fetchApi<Order>(`/api/orders/${id}`),
|
|
208
|
+
|
|
209
|
+
create: (data: CreateOrderRequest) =>
|
|
210
|
+
fetchApi<Order>('/api/orders', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body: JSON.stringify(data),
|
|
213
|
+
}),
|
|
214
|
+
|
|
215
|
+
update: (id: number, data: UpdateOrderRequest) =>
|
|
216
|
+
fetchApi<void>(`/api/orders/${id}`, {
|
|
217
|
+
method: 'PUT',
|
|
218
|
+
body: JSON.stringify(data),
|
|
219
|
+
}),
|
|
220
|
+
|
|
221
|
+
delete: (id: number) =>
|
|
222
|
+
fetchApi<void>(`/api/orders/${id}`, {
|
|
223
|
+
method: 'DELETE',
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Types (gerado do OpenAPI)
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// lib/types.ts
|
|
233
|
+
export interface Order {
|
|
234
|
+
id: number;
|
|
235
|
+
orderNumber: string;
|
|
236
|
+
customerId: number;
|
|
237
|
+
customerName: string;
|
|
238
|
+
total: number;
|
|
239
|
+
status: OrderStatus;
|
|
240
|
+
createdAt: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type OrderStatus = 'Pending' | 'Processing' | 'Completed' | 'Cancelled';
|
|
244
|
+
|
|
245
|
+
export interface CreateOrderRequest {
|
|
246
|
+
customerId: number;
|
|
247
|
+
items: OrderItem[];
|
|
248
|
+
notes?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface PaginatedResult<T> {
|
|
252
|
+
items: T[];
|
|
253
|
+
totalCount: number;
|
|
254
|
+
page: number;
|
|
255
|
+
pageSize: number;
|
|
256
|
+
totalPages: number;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Server Component (App Router)
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// app/orders/page.tsx
|
|
264
|
+
import { api } from '@/lib/api';
|
|
265
|
+
import { OrderList } from '@/components/orders/OrderList';
|
|
266
|
+
|
|
267
|
+
export default async function OrdersPage() {
|
|
268
|
+
const orders = await api.orders.getAll();
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className="container mx-auto p-4">
|
|
272
|
+
<div className="flex justify-between items-center mb-6">
|
|
273
|
+
<h1 className="text-2xl font-bold">Pedidos</h1>
|
|
274
|
+
<a href="/orders/new" className="btn btn-primary">
|
|
275
|
+
Novo Pedido
|
|
276
|
+
</a>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<OrderList initialOrders={orders} />
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Client Component
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
'use client';
|
|
289
|
+
|
|
290
|
+
// components/orders/OrderList.tsx
|
|
291
|
+
import { useState } from 'react';
|
|
292
|
+
import { Order, PaginatedResult } from '@/lib/types';
|
|
293
|
+
import { api } from '@/lib/api';
|
|
294
|
+
|
|
295
|
+
interface Props {
|
|
296
|
+
initialOrders: PaginatedResult<Order>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function OrderList({ initialOrders }: Props) {
|
|
300
|
+
const [orders, setOrders] = useState(initialOrders);
|
|
301
|
+
const [loading, setLoading] = useState(false);
|
|
302
|
+
|
|
303
|
+
async function loadPage(page: number) {
|
|
304
|
+
setLoading(true);
|
|
305
|
+
try {
|
|
306
|
+
const result = await api.orders.getAll({ page });
|
|
307
|
+
setOrders(result);
|
|
308
|
+
} finally {
|
|
309
|
+
setLoading(false);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div>
|
|
315
|
+
<table className="w-full">
|
|
316
|
+
<thead>
|
|
317
|
+
<tr>
|
|
318
|
+
<th>Número</th>
|
|
319
|
+
<th>Cliente</th>
|
|
320
|
+
<th>Total</th>
|
|
321
|
+
<th>Status</th>
|
|
322
|
+
</tr>
|
|
323
|
+
</thead>
|
|
324
|
+
<tbody>
|
|
325
|
+
{orders.items.map((order) => (
|
|
326
|
+
<tr key={order.id}>
|
|
327
|
+
<td>{order.orderNumber}</td>
|
|
328
|
+
<td>{order.customerName}</td>
|
|
329
|
+
<td>{order.total.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })}</td>
|
|
330
|
+
<td><StatusBadge status={order.status} /></td>
|
|
331
|
+
</tr>
|
|
332
|
+
))}
|
|
333
|
+
</tbody>
|
|
334
|
+
</table>
|
|
335
|
+
|
|
336
|
+
<Pagination
|
|
337
|
+
currentPage={orders.page}
|
|
338
|
+
totalPages={orders.totalPages}
|
|
339
|
+
onPageChange={loadPage}
|
|
340
|
+
disabled={loading}
|
|
341
|
+
/>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Docker Compose
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
# docker-compose.yml
|
|
351
|
+
services:
|
|
352
|
+
backend:
|
|
353
|
+
build:
|
|
354
|
+
context: ./backend
|
|
355
|
+
dockerfile: Dockerfile
|
|
356
|
+
ports:
|
|
357
|
+
- "5000:8080"
|
|
358
|
+
environment:
|
|
359
|
+
- ConnectionStrings__Default=Server=db;Database=App;User=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=true
|
|
360
|
+
- Frontend__Url=http://localhost:3000
|
|
361
|
+
depends_on:
|
|
362
|
+
- db
|
|
363
|
+
|
|
364
|
+
frontend:
|
|
365
|
+
build:
|
|
366
|
+
context: ./frontend
|
|
367
|
+
dockerfile: Dockerfile
|
|
368
|
+
ports:
|
|
369
|
+
- "3000:3000"
|
|
370
|
+
environment:
|
|
371
|
+
- NEXT_PUBLIC_API_URL=http://localhost:5000
|
|
372
|
+
|
|
373
|
+
db:
|
|
374
|
+
image: mcr.microsoft.com/mssql/server:2022-latest
|
|
375
|
+
environment:
|
|
376
|
+
- ACCEPT_EULA=Y
|
|
377
|
+
- SA_PASSWORD=YourStrong!Passw0rd
|
|
378
|
+
ports:
|
|
379
|
+
- "1433:1433"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Documentação de Referência
|
|
383
|
+
|
|
384
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
385
|
+
- [ASP.NET Web API](https://learn.microsoft.com/en-us/aspnet/core/web-api/)
|
|
386
|
+
- [React Documentation](https://react.dev/)
|
|
387
|
+
- [Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis)
|
|
388
|
+
|
|
389
|
+
## Checklist de Projeto
|
|
390
|
+
|
|
391
|
+
- [ ] Backend com Minimal APIs
|
|
392
|
+
- [ ] OpenAPI/Swagger configurado
|
|
393
|
+
- [ ] CORS configurado para frontend
|
|
394
|
+
- [ ] Frontend com App Router (Next.js 15+)
|
|
395
|
+
- [ ] API client tipado
|
|
396
|
+
- [ ] Types gerados do OpenAPI
|
|
397
|
+
- [ ] Docker Compose para dev
|
|
398
|
+
- [ ] Environment variables configuradas
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
*MORPH-SPEC by Polymorphism Tech*
|