moicle 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 +201 -0
- package/assets/agents/developers/flutter-mobile-dev.md +69 -0
- package/assets/agents/developers/go-backend-dev.md +57 -0
- package/assets/agents/developers/laravel-backend-dev.md +123 -0
- package/assets/agents/developers/react-frontend-dev.md +69 -0
- package/assets/agents/developers/remix-fullstack-dev.md +69 -0
- package/assets/agents/utilities/api-designer.md +76 -0
- package/assets/agents/utilities/clean-architect.md +83 -0
- package/assets/agents/utilities/code-reviewer.md +76 -0
- package/assets/agents/utilities/db-designer.md +68 -0
- package/assets/agents/utilities/devops.md +71 -0
- package/assets/agents/utilities/docs-writer.md +75 -0
- package/assets/agents/utilities/perf-optimizer.md +87 -0
- package/assets/agents/utilities/refactor.md +173 -0
- package/assets/agents/utilities/security-audit.md +203 -0
- package/assets/agents/utilities/test-writer.md +139 -0
- package/assets/architecture/clean-architecture.md +143 -0
- package/assets/architecture/flutter-mobile.md +304 -0
- package/assets/architecture/go-backend.md +217 -0
- package/assets/architecture/laravel-backend.md +303 -0
- package/assets/architecture/monorepo.md +162 -0
- package/assets/architecture/react-frontend.md +268 -0
- package/assets/architecture/remix-fullstack.md +272 -0
- package/assets/commands/bootstrap.md +98 -0
- package/assets/commands/brainstorm.md +440 -0
- package/assets/skills/feature-workflow/SKILL.md +298 -0
- package/assets/skills/hotfix-workflow/SKILL.md +368 -0
- package/assets/templates/flutter/CLAUDE.md +454 -0
- package/assets/templates/go-gin/CLAUDE.md +244 -0
- package/assets/templates/monorepo/CLAUDE.md +362 -0
- package/assets/templates/react-vite/CLAUDE.md +304 -0
- package/assets/templates/remix/CLAUDE.md +304 -0
- package/bin/cli.js +76 -0
- package/dist/commands/disable.d.ts +3 -0
- package/dist/commands/disable.d.ts.map +1 -0
- package/dist/commands/disable.js +188 -0
- package/dist/commands/disable.js.map +1 -0
- package/dist/commands/enable.d.ts +3 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/enable.js +191 -0
- package/dist/commands/enable.js.map +1 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +290 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +75 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/postinstall.d.ts +2 -0
- package/dist/commands/postinstall.d.ts.map +1 -0
- package/dist/commands/postinstall.js +25 -0
- package/dist/commands/postinstall.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +118 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.d.ts +3 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +178 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +95 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/symlink.d.ts +24 -0
- package/dist/utils/symlink.d.ts.map +1 -0
- package/dist/utils/symlink.js +313 -0
- package/dist/utils/symlink.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Laravel Backend Structure
|
|
2
|
+
|
|
3
|
+
> Simple Domain + UseCase pattern for Laravel
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{project}/
|
|
9
|
+
├── app/
|
|
10
|
+
│ ├── Domain/ # Business logic by feature
|
|
11
|
+
│ │ └── {Feature}/
|
|
12
|
+
│ │ ├── Entities/ # Value objects, DTOs
|
|
13
|
+
│ │ ├── Events/ # Domain events
|
|
14
|
+
│ │ ├── Exceptions/ # Feature-specific exceptions
|
|
15
|
+
│ │ ├── Listeners/ # Event listeners
|
|
16
|
+
│ │ └── UseCase/ # Action classes
|
|
17
|
+
│ │ ├── GetUserUseCase.php
|
|
18
|
+
│ │ ├── CreateUserUseCase.php
|
|
19
|
+
│ │ └── UpdateUserUseCase.php
|
|
20
|
+
│ ├── Http/
|
|
21
|
+
│ │ ├── Controllers/
|
|
22
|
+
│ │ │ ├── Api/ # API controllers
|
|
23
|
+
│ │ │ └── Web/ # Web controllers
|
|
24
|
+
│ │ ├── Middleware/
|
|
25
|
+
│ │ ├── Requests/ # Form requests (validation)
|
|
26
|
+
│ │ └── Resources/ # API resources
|
|
27
|
+
│ ├── Models/ # Eloquent models
|
|
28
|
+
│ ├── Services/ # Shared services (cache, etc.)
|
|
29
|
+
│ └── Providers/
|
|
30
|
+
├── config/
|
|
31
|
+
├── database/
|
|
32
|
+
│ ├── factories/
|
|
33
|
+
│ ├── migrations/
|
|
34
|
+
│ └── seeders/
|
|
35
|
+
├── routes/
|
|
36
|
+
│ ├── api.php
|
|
37
|
+
│ └── web.php
|
|
38
|
+
├── tests/
|
|
39
|
+
│ ├── Feature/
|
|
40
|
+
│ └── Unit/
|
|
41
|
+
├── .claude/
|
|
42
|
+
├── CLAUDE.md
|
|
43
|
+
└── composer.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Architecture Pattern
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Controller → UseCase → Model (Eloquent)
|
|
50
|
+
↓ ↓
|
|
51
|
+
Request Services (optional: cache, external APIs)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Simple flow:**
|
|
55
|
+
1. Controller receives request
|
|
56
|
+
2. Controller injects and calls UseCase
|
|
57
|
+
3. UseCase contains business logic
|
|
58
|
+
4. UseCase uses Eloquent Models directly
|
|
59
|
+
5. Controller returns response
|
|
60
|
+
|
|
61
|
+
## Domain Structure Example
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
app/Domain/
|
|
65
|
+
├── Story/
|
|
66
|
+
│ ├── Entities/
|
|
67
|
+
│ │ └── Story.php
|
|
68
|
+
│ ├── Events/
|
|
69
|
+
│ │ └── StoryCreated.php
|
|
70
|
+
│ ├── Exceptions/
|
|
71
|
+
│ │ ├── StoryNotFound.php
|
|
72
|
+
│ │ └── StoryContentException.php
|
|
73
|
+
│ ├── Listeners/
|
|
74
|
+
│ │ └── NotifyOnStoryCreated.php
|
|
75
|
+
│ └── UseCase/
|
|
76
|
+
│ ├── GetStoryBySlugUseCase.php
|
|
77
|
+
│ ├── GetAllStoriesUseCase.php
|
|
78
|
+
│ ├── CreateStoryUseCase.php
|
|
79
|
+
│ ├── UpdateStoryUseCase.php
|
|
80
|
+
│ └── DeleteStoryUseCase.php
|
|
81
|
+
├── User/
|
|
82
|
+
│ └── UseCase/
|
|
83
|
+
│ ├── GetUserUseCase.php
|
|
84
|
+
│ └── CreateUserUseCase.php
|
|
85
|
+
└── Shared/
|
|
86
|
+
└── Payload/
|
|
87
|
+
├── Filter.php
|
|
88
|
+
└── Sorter.php
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Key Files
|
|
92
|
+
|
|
93
|
+
### UseCase Example
|
|
94
|
+
|
|
95
|
+
```php
|
|
96
|
+
<?php
|
|
97
|
+
// app/Domain/Story/UseCase/GetStoryBySlugUseCase.php
|
|
98
|
+
|
|
99
|
+
namespace App\Domain\Story\UseCase;
|
|
100
|
+
|
|
101
|
+
use App\Domain\Story\Exceptions\StoryNotFound;
|
|
102
|
+
use App\Models\Story;
|
|
103
|
+
use App\Services\Cache\CacheService;
|
|
104
|
+
|
|
105
|
+
class GetStoryBySlugUseCase
|
|
106
|
+
{
|
|
107
|
+
public function __construct(
|
|
108
|
+
protected Story $story,
|
|
109
|
+
protected CacheService $cacheService
|
|
110
|
+
) {}
|
|
111
|
+
|
|
112
|
+
public function execute(string $slug): object
|
|
113
|
+
{
|
|
114
|
+
// Check cache first
|
|
115
|
+
$cached = $this->cacheService->get("story:{$slug}");
|
|
116
|
+
if ($cached) {
|
|
117
|
+
return $cached;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Query with Eloquent
|
|
121
|
+
$story = $this->story->newQuery()
|
|
122
|
+
->select(['id', 'title', 'slug', 'description', 'author_id'])
|
|
123
|
+
->with(['genres:id,name,slug', 'author:id,name,slug'])
|
|
124
|
+
->where('slug', $slug)
|
|
125
|
+
->published()
|
|
126
|
+
->first();
|
|
127
|
+
|
|
128
|
+
if (!$story) {
|
|
129
|
+
throw new StoryNotFound("Story '{$slug}' not found");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Cache result
|
|
133
|
+
$this->cacheService->put("story:{$slug}", $story, 60 * 60);
|
|
134
|
+
|
|
135
|
+
return $story;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Controller Example
|
|
141
|
+
|
|
142
|
+
```php
|
|
143
|
+
<?php
|
|
144
|
+
// app/Http/Controllers/Web/StoryController.php
|
|
145
|
+
|
|
146
|
+
namespace App\Http\Controllers\Web;
|
|
147
|
+
|
|
148
|
+
use App\Domain\Story\UseCase\GetStoryBySlugUseCase;
|
|
149
|
+
use App\Domain\Story\UseCase\GetAllStoriesUseCase;
|
|
150
|
+
use App\Domain\Story\Exceptions\StoryNotFound;
|
|
151
|
+
use App\Http\Controllers\Controller;
|
|
152
|
+
use Illuminate\Http\Request;
|
|
153
|
+
|
|
154
|
+
class StoryController extends Controller
|
|
155
|
+
{
|
|
156
|
+
public function __construct(
|
|
157
|
+
protected GetStoryBySlugUseCase $getStoryBySlugUseCase
|
|
158
|
+
) {}
|
|
159
|
+
|
|
160
|
+
public function index(
|
|
161
|
+
Request $request,
|
|
162
|
+
GetAllStoriesUseCase $getAllStoriesUseCase
|
|
163
|
+
) {
|
|
164
|
+
$filters = [
|
|
165
|
+
'search' => $request->query('search'),
|
|
166
|
+
'genre' => $request->query('genre'),
|
|
167
|
+
'sort' => $request->query('sort', 'latest'),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
$stories = $getAllStoriesUseCase->execute($filters);
|
|
171
|
+
|
|
172
|
+
return view('pages.story.index', compact('stories'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public function show(string $slug)
|
|
176
|
+
{
|
|
177
|
+
try {
|
|
178
|
+
$story = $this->getStoryBySlugUseCase->execute($slug);
|
|
179
|
+
return view('pages.story.show', compact('story'));
|
|
180
|
+
} catch (StoryNotFound $e) {
|
|
181
|
+
abort(404);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Exception Example
|
|
188
|
+
|
|
189
|
+
```php
|
|
190
|
+
<?php
|
|
191
|
+
// app/Domain/Story/Exceptions/StoryNotFound.php
|
|
192
|
+
|
|
193
|
+
namespace App\Domain\Story\Exceptions;
|
|
194
|
+
|
|
195
|
+
use Exception;
|
|
196
|
+
|
|
197
|
+
class StoryNotFound extends Exception
|
|
198
|
+
{
|
|
199
|
+
//
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Shared Payload Example
|
|
204
|
+
|
|
205
|
+
```php
|
|
206
|
+
<?php
|
|
207
|
+
// app/Domain/Shared/Payload/Filter.php
|
|
208
|
+
|
|
209
|
+
namespace App\Domain\Shared\Payload;
|
|
210
|
+
|
|
211
|
+
class Filter
|
|
212
|
+
{
|
|
213
|
+
public function __construct(
|
|
214
|
+
public string $field,
|
|
215
|
+
public string $operator,
|
|
216
|
+
public mixed $value
|
|
217
|
+
) {}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Conventions
|
|
222
|
+
|
|
223
|
+
| Item | Convention | Example |
|
|
224
|
+
|------|------------|---------|
|
|
225
|
+
| UseCase | PascalCase + UseCase | `GetStoryBySlugUseCase` |
|
|
226
|
+
| Controller | PascalCase + Controller | `StoryController` |
|
|
227
|
+
| Model | Singular PascalCase | `Story` |
|
|
228
|
+
| Exception | PascalCase | `StoryNotFound` |
|
|
229
|
+
| Event | PascalCase | `StoryCreated` |
|
|
230
|
+
| Request | PascalCase + Request | `StoreStoryRequest` |
|
|
231
|
+
| Resource | PascalCase + Resource | `StoryResource` |
|
|
232
|
+
|
|
233
|
+
## UseCase Naming
|
|
234
|
+
|
|
235
|
+
| Action | Naming Pattern | Example |
|
|
236
|
+
|--------|----------------|---------|
|
|
237
|
+
| Get single | `Get{Entity}ByXxxUseCase` | `GetStoryBySlugUseCase` |
|
|
238
|
+
| Get list | `Get{Entities}UseCase` | `GetAllStoriesUseCase` |
|
|
239
|
+
| Get filtered | `GetXxxUseCase` | `GetLatestStoriesUseCase` |
|
|
240
|
+
| Create | `Create{Entity}UseCase` | `CreateStoryUseCase` |
|
|
241
|
+
| Update | `Update{Entity}UseCase` | `UpdateStoryUseCase` |
|
|
242
|
+
| Delete | `Delete{Entity}UseCase` | `DeleteStoryUseCase` |
|
|
243
|
+
| Search | `Search{Entities}UseCase` | `SearchStoriesUseCase` |
|
|
244
|
+
| Action | `{Action}{Entity}UseCase` | `PublishStoryUseCase` |
|
|
245
|
+
|
|
246
|
+
## When to Create UseCase
|
|
247
|
+
|
|
248
|
+
**Create UseCase when:**
|
|
249
|
+
- Business logic is reusable across controllers
|
|
250
|
+
- Logic is complex (multiple model interactions)
|
|
251
|
+
- Logic needs caching, events, or validation
|
|
252
|
+
- Action is a distinct business operation
|
|
253
|
+
|
|
254
|
+
**Skip UseCase when:**
|
|
255
|
+
- Simple CRUD with no business logic
|
|
256
|
+
- Direct model query in controller is clearer
|
|
257
|
+
|
|
258
|
+
## Testing
|
|
259
|
+
|
|
260
|
+
```php
|
|
261
|
+
<?php
|
|
262
|
+
// tests/Unit/Domain/Story/GetStoryBySlugUseCaseTest.php
|
|
263
|
+
|
|
264
|
+
namespace Tests\Unit\Domain\Story;
|
|
265
|
+
|
|
266
|
+
use Tests\TestCase;
|
|
267
|
+
use App\Domain\Story\UseCase\GetStoryBySlugUseCase;
|
|
268
|
+
use App\Domain\Story\Exceptions\StoryNotFound;
|
|
269
|
+
use App\Models\Story;
|
|
270
|
+
use App\Services\Cache\CacheService;
|
|
271
|
+
use Mockery;
|
|
272
|
+
|
|
273
|
+
class GetStoryBySlugUseCaseTest extends TestCase
|
|
274
|
+
{
|
|
275
|
+
public function test_returns_story_when_found(): void
|
|
276
|
+
{
|
|
277
|
+
$story = Story::factory()->create(['slug' => 'test-story']);
|
|
278
|
+
$cacheService = Mockery::mock(CacheService::class);
|
|
279
|
+
$cacheService->shouldReceive('get')->andReturn(null);
|
|
280
|
+
$cacheService->shouldReceive('put');
|
|
281
|
+
|
|
282
|
+
$useCase = new GetStoryBySlugUseCase(new Story(), $cacheService);
|
|
283
|
+
$result = $useCase->execute('test-story');
|
|
284
|
+
|
|
285
|
+
$this->assertEquals($story->id, $result->id);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public function test_throws_exception_when_not_found(): void
|
|
289
|
+
{
|
|
290
|
+
$this->expectException(StoryNotFound::class);
|
|
291
|
+
|
|
292
|
+
$cacheService = Mockery::mock(CacheService::class);
|
|
293
|
+
$cacheService->shouldReceive('get')->andReturn(null);
|
|
294
|
+
|
|
295
|
+
$useCase = new GetStoryBySlugUseCase(new Story(), $cacheService);
|
|
296
|
+
$useCase->execute('non-existent-slug');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
# Documentation
|
|
301
|
+
|
|
302
|
+
- When you create a new UseCase, make sure to document it in the project dir `docs/{domain}/{usecase}.md` file.
|
|
303
|
+
- The usecase spectation documentation flow UML or Cockburn
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Monorepo Structure
|
|
2
|
+
|
|
3
|
+
> Reference: [Clean Architecture](./clean-architecture.md)
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{project}/
|
|
9
|
+
├── apps/
|
|
10
|
+
│ ├── web/ # React frontend
|
|
11
|
+
│ │ └── (see react-frontend.md)
|
|
12
|
+
│ ├── api/ # Go/Node backend
|
|
13
|
+
│ │ └── (see go-backend.md or remix-fullstack.md)
|
|
14
|
+
│ └── mobile/ # Flutter (optional)
|
|
15
|
+
│ └── (see flutter-mobile.md)
|
|
16
|
+
├── packages/
|
|
17
|
+
│ ├── shared/
|
|
18
|
+
│ │ ├── types/ # Shared TypeScript types
|
|
19
|
+
│ │ ├── utils/ # Shared utilities
|
|
20
|
+
│ │ └── config/ # Shared configuration
|
|
21
|
+
│ ├── ui/ # Shared UI components
|
|
22
|
+
│ │ ├── src/
|
|
23
|
+
│ │ │ └── components/
|
|
24
|
+
│ │ └── package.json
|
|
25
|
+
│ └── api-client/ # Generated API client
|
|
26
|
+
│ ├── src/
|
|
27
|
+
│ └── package.json
|
|
28
|
+
├── tools/
|
|
29
|
+
│ ├── scripts/ # Build/deploy scripts
|
|
30
|
+
│ └── generators/ # Code generators
|
|
31
|
+
├── .claude/
|
|
32
|
+
│ └── agents/
|
|
33
|
+
├── CLAUDE.md
|
|
34
|
+
├── package.json # Root package.json
|
|
35
|
+
├── pnpm-workspace.yaml # (If pnpm) Workspace config
|
|
36
|
+
├── turbo.json # Turborepo config
|
|
37
|
+
└── README.md
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Package Manager Selection
|
|
41
|
+
|
|
42
|
+
**Ask the user to choose a package manager:**
|
|
43
|
+
|
|
44
|
+
1. **pnpm** (Recommended):
|
|
45
|
+
* Uses `pnpm-workspace.yaml`.
|
|
46
|
+
2. **Bun** / **Yarn** / **NPM**:
|
|
47
|
+
* Uses `workspaces` in `package.json`.
|
|
48
|
+
|
|
49
|
+
## Workspace Configuration
|
|
50
|
+
|
|
51
|
+
### Option 1: pnpm (pnpm-workspace.yaml)
|
|
52
|
+
```yaml
|
|
53
|
+
packages:
|
|
54
|
+
- 'apps/*'
|
|
55
|
+
- 'packages/*'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Option 2: Bun / Yarn / NPM (package.json)
|
|
59
|
+
Add to root `package.json`:
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"workspaces": [
|
|
63
|
+
"apps/*",
|
|
64
|
+
"packages/*"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### turbo.json
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"$schema": "https://turbo.build/schema.json",
|
|
73
|
+
"pipeline": {
|
|
74
|
+
"build": {
|
|
75
|
+
"dependsOn": ["^build"],
|
|
76
|
+
"outputs": ["dist/**", ".next/**"]
|
|
77
|
+
},
|
|
78
|
+
"dev": {
|
|
79
|
+
"cache": false,
|
|
80
|
+
"persistent": true
|
|
81
|
+
},
|
|
82
|
+
"lint": {},
|
|
83
|
+
"test": {
|
|
84
|
+
"dependsOn": ["build"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Root package.json
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"name": "monorepo",
|
|
94
|
+
"private": true,
|
|
95
|
+
"scripts": {
|
|
96
|
+
"dev": "turbo run dev",
|
|
97
|
+
"build": "turbo run build",
|
|
98
|
+
"lint": "turbo run lint",
|
|
99
|
+
"test": "turbo run test",
|
|
100
|
+
"dev:web": "turbo run dev --filter=web",
|
|
101
|
+
"dev:api": "turbo run dev --filter=api"
|
|
102
|
+
},
|
|
103
|
+
"devDependencies": {
|
|
104
|
+
"turbo": "latest"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Shared Packages
|
|
110
|
+
|
|
111
|
+
### packages/shared/types
|
|
112
|
+
```typescript
|
|
113
|
+
// packages/shared/types/src/user.ts
|
|
114
|
+
export interface User {
|
|
115
|
+
id: string;
|
|
116
|
+
email: string;
|
|
117
|
+
name: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface CreateUserDTO {
|
|
121
|
+
email: string;
|
|
122
|
+
name: string;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### packages/ui
|
|
127
|
+
```typescript
|
|
128
|
+
// packages/ui/src/components/Button.tsx
|
|
129
|
+
export interface ButtonProps {
|
|
130
|
+
variant: 'primary' | 'secondary';
|
|
131
|
+
children: React.ReactNode;
|
|
132
|
+
onClick?: () => void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function Button({ variant, children, onClick }: ButtonProps) {
|
|
136
|
+
return (
|
|
137
|
+
<button className={`btn btn-${variant}`} onClick={onClick}>
|
|
138
|
+
{children}
|
|
139
|
+
</button>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## Conventions
|
|
147
|
+
|
|
148
|
+
| Item | Location | Example |
|
|
149
|
+
|------|----------|---------|
|
|
150
|
+
| Shared types | `packages/shared/types` | `User`, `ApiResponse` |
|
|
151
|
+
| Shared UI | `packages/ui` | `Button`, `Modal` |
|
|
152
|
+
| API client | `packages/api-client` | Auto-generated |
|
|
153
|
+
| App-specific | `apps/{app}/src` | Features, pages |
|
|
154
|
+
|
|
155
|
+
## Cross-App Import
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// apps/web/src/features/users/UserList.tsx
|
|
159
|
+
import { User } from '@repo/shared/types';
|
|
160
|
+
import { Button } from '@repo/ui';
|
|
161
|
+
import { apiClient } from '@repo/api-client';
|
|
162
|
+
```
|