@yoms/create-monorepo 1.0.3 → 1.2.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/dist/index.js CHANGED
@@ -460,6 +460,10 @@ async function mergeEnvFile(baseDir, additionsPath) {
460
460
 
461
461
  // src/generators/backend.generator.ts
462
462
  var __dirname = path4.dirname(fileURLToPath(import.meta.url));
463
+ var getPackageRoot = () => {
464
+ const dirname = __dirname;
465
+ return path4.resolve(dirname, "..");
466
+ };
463
467
  var BackendGenerator = class extends BaseGenerator {
464
468
  config;
465
469
  constructor(options) {
@@ -475,17 +479,18 @@ var BackendGenerator = class extends BaseGenerator {
475
479
  __HAS_REDIS__: String(this.config.includeRedis),
476
480
  __HAS_SMTP__: String(this.config.includeSmtp)
477
481
  });
482
+ const packageRoot = getPackageRoot();
478
483
  let templatePath;
479
484
  if (this.config.type === "web") {
480
- templatePath = path4.join(__dirname, "../../templates", `backend-${this.config.framework}`, "base");
485
+ templatePath = path4.join(packageRoot, "templates", `backend-${this.config.framework}`, "base");
481
486
  } else if (this.config.type === "worker") {
482
- templatePath = path4.join(__dirname, "../../templates", "backend-worker", "base");
487
+ templatePath = path4.join(packageRoot, "templates", "backend-worker", "base");
483
488
  } else {
484
- templatePath = path4.join(__dirname, "../../templates", "backend-cli", "base");
489
+ templatePath = path4.join(packageRoot, "templates", "backend-cli", "base");
485
490
  }
486
491
  await copyDirRecursive(templatePath, backendDir, tokens);
487
492
  if (this.config.type === "web") {
488
- const featuresPath = path4.join(__dirname, "../../templates", `backend-${this.config.framework}`, "features");
493
+ const featuresPath = path4.join(packageRoot, "templates", `backend-${this.config.framework}`, "features");
489
494
  if (this.config.database) {
490
495
  const dbFeaturePath = path4.join(featuresPath, `${this.config.database}-prisma`);
491
496
  await mergeFeature(backendDir, dbFeaturePath, tokens);
@@ -510,6 +515,10 @@ var BackendGenerator = class extends BaseGenerator {
510
515
  import path5 from "path";
511
516
  import { fileURLToPath as fileURLToPath2 } from "url";
512
517
  var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
518
+ var getPackageRoot2 = () => {
519
+ const dirname = __dirname2;
520
+ return path5.resolve(dirname, "..");
521
+ };
513
522
  var FrontendGenerator = class extends BaseGenerator {
514
523
  config;
515
524
  constructor(options) {
@@ -522,7 +531,8 @@ var FrontendGenerator = class extends BaseGenerator {
522
531
  const tokens = this.getTokens({
523
532
  __API_URL__: this.config.apiUrl
524
533
  });
525
- const templatePath = path5.join(__dirname2, "../../templates", "frontend-nextjs", "base");
534
+ const packageRoot = getPackageRoot2();
535
+ const templatePath = path5.join(packageRoot, "templates", "frontend-nextjs", "base");
526
536
  await copyDirRecursive(templatePath, frontendDir, tokens);
527
537
  }
528
538
  };
@@ -531,12 +541,17 @@ var FrontendGenerator = class extends BaseGenerator {
531
541
  import path6 from "path";
532
542
  import { fileURLToPath as fileURLToPath3 } from "url";
533
543
  var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
544
+ var getPackageRoot3 = () => {
545
+ const dirname = __dirname3;
546
+ return path6.resolve(dirname, "..");
547
+ };
534
548
  var SharedGenerator = class extends BaseGenerator {
535
549
  async generate() {
536
550
  const sharedDir = path6.join(this.options.projectDir, "packages", "shared");
537
551
  await ensureDir(sharedDir);
538
552
  const tokens = this.getTokens();
539
- const templatePath = path6.join(__dirname3, "../../templates", "shared", "base");
553
+ const packageRoot = getPackageRoot3();
554
+ const templatePath = path6.join(packageRoot, "templates", "shared", "base");
540
555
  await copyDirRecursive(templatePath, sharedDir, tokens);
541
556
  }
542
557
  };
@@ -641,7 +656,7 @@ async function createMonorepo(targetDir) {
641
656
 
642
657
  // src/index.ts
643
658
  var cli = cac("create-monorepo");
644
- cli.command("[dir]", "Create a brand new monorepo project").action(async (dir) => {
659
+ cli.command("[dir]", "Create a new monorepo project").action(async (dir) => {
645
660
  await createMonorepo(dir);
646
661
  });
647
662
  cli.help();
package/package.json CHANGED
@@ -1,24 +1,11 @@
1
1
  {
2
2
  "name": "@yoms/create-monorepo",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "CLI tool to scaffold monorepo projects from templates",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-monorepo": "./dist/index.js"
8
8
  },
9
- "scripts": {
10
- "dev": "tsx src/index.ts",
11
- "build": "tsup src/index.ts --format esm --clean",
12
- "typecheck": "tsc --noEmit",
13
- "lint": "eslint src --ext .ts",
14
- "format": "prettier --write \"src/**/*.ts\"",
15
- "test:generate": "tsx scripts/test-generate.ts",
16
- "test:validate": "tsx scripts/validate-templates.ts",
17
- "changeset": "changeset",
18
- "version": "changeset version",
19
- "release": "pnpm build && changeset publish",
20
- "prepublishOnly": "pnpm build && pnpm typecheck"
21
- },
22
9
  "keywords": [
23
10
  "monorepo",
24
11
  "template",
@@ -70,5 +57,17 @@
70
57
  },
71
58
  "engines": {
72
59
  "node": ">=18.0.0"
60
+ },
61
+ "scripts": {
62
+ "dev": "tsx src/index.ts",
63
+ "build": "tsup src/index.ts --format esm --clean",
64
+ "typecheck": "tsc --noEmit",
65
+ "lint": "eslint src --ext .ts",
66
+ "format": "prettier --write \"src/**/*.ts\"",
67
+ "test:generate": "tsx scripts/test-generate.ts",
68
+ "test:validate": "tsx scripts/validate-templates.ts",
69
+ "changeset": "changeset",
70
+ "version": "changeset version && pnpm install --lockfile-only",
71
+ "release": "pnpm build && changeset publish"
73
72
  }
74
- }
73
+ }
@@ -0,0 +1,326 @@
1
+ # AGENT.md - Backend API
2
+
3
+ This file provides guidance to AI agents (Claude, Cursor, etc.) when working with this backend API.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Hono-based backend API in a monorepo workspace. It's designed for high performance, full type safety, and production-ready patterns.
8
+
9
+ ## Tech Stack
10
+
11
+ - **Framework**: Hono (lightweight, fast, type-safe)
12
+ - **Language**: TypeScript with strict mode
13
+ - **Runtime**: Node.js (compatible with Bun, Deno, Cloudflare Workers)
14
+ - **Validation**: Zod schemas
15
+ - **Logger**: Winston with structured logging
16
+ - **Build Tool**: tsup (fast ESM bundler)
17
+ - **Testing**: Vitest with coverage
18
+
19
+ ## Development Commands
20
+
21
+ ```bash
22
+ # Development
23
+ pnpm dev # Start with hot reload
24
+ pnpm build # Build for production
25
+ pnpm start # Run production build
26
+ pnpm test # Run tests
27
+ pnpm test:watch # Watch mode
28
+ pnpm test:coverage # Coverage report
29
+ pnpm typecheck # Type checking
30
+ ```
31
+
32
+ ## Project Structure
33
+
34
+ ```
35
+ src/
36
+ ├── config/ # Configuration (env, logger, db)
37
+ ├── lib/ # Utilities and helpers
38
+ │ ├── errors.ts # Custom error classes
39
+ │ └── response.ts # Response helpers
40
+ ├── middleware/ # Hono middleware
41
+ │ ├── cors.middleware.ts
42
+ │ ├── error.middleware.ts
43
+ │ ├── logger.middleware.ts
44
+ │ └── rate-limit.middleware.ts
45
+ ├── routes/ # API route handlers
46
+ │ └── health.route.ts
47
+ ├── services/ # Business logic (if database enabled)
48
+ ├── types/ # TypeScript types
49
+ └── index.ts # App entry point
50
+ ```
51
+
52
+ ## Architecture Patterns
53
+
54
+ ### Error Handling
55
+
56
+ Use custom error classes for consistent error responses:
57
+
58
+ ```typescript
59
+ import { NotFoundError, BadRequestError } from '../lib/errors.js';
60
+
61
+ // Throw custom errors - they're automatically handled
62
+ throw new NotFoundError('User not found');
63
+ throw new BadRequestError('Invalid email format');
64
+ ```
65
+
66
+ Available errors:
67
+ - `BadRequestError` (400)
68
+ - `UnauthorizedError` (401)
69
+ - `ForbiddenError` (403)
70
+ - `NotFoundError` (404)
71
+ - `ConflictError` (409)
72
+ - `ValidationError` (422)
73
+ - `TooManyRequestsError` (429)
74
+ - `InternalServerError` (500)
75
+
76
+ ### Response Helpers
77
+
78
+ Use standardized response helpers:
79
+
80
+ ```typescript
81
+ import { success, created, noContent, paginated } from '../lib/response.js';
82
+
83
+ // Success response (200)
84
+ return success(c, { id: '123', name: 'John' });
85
+
86
+ // Created response (201)
87
+ return created(c, newUser);
88
+
89
+ // No content (204)
90
+ return noContent(c);
91
+
92
+ // Paginated response
93
+ return paginated(c, users, page, limit, total);
94
+ ```
95
+
96
+ ### Route Structure
97
+
98
+ Follow this pattern for new routes:
99
+
100
+ ```typescript
101
+ import { Hono } from 'hono';
102
+ import { zValidator } from '@hono/zod-validator';
103
+ import { success } from '../lib/response.js';
104
+ import { CreateItemSchema } from '@__PROJECT_NAME__/shared';
105
+
106
+ const items = new Hono();
107
+
108
+ // List items with pagination
109
+ items.get('/', zValidator('query', paginationSchema), async (c) => {
110
+ const { page, limit } = c.req.valid('query');
111
+ const result = await ItemService.getAll(page, limit);
112
+ return paginated(c, result.items, page, limit, result.total);
113
+ });
114
+
115
+ // Get by ID
116
+ items.get('/:id', async (c) => {
117
+ const id = c.req.param('id');
118
+ const item = await ItemService.getById(id);
119
+ return success(c, item);
120
+ });
121
+
122
+ // Create
123
+ items.post('/', zValidator('json', CreateItemSchema), async (c) => {
124
+ const data = c.req.valid('json');
125
+ const item = await ItemService.create(data);
126
+ return created(c, item);
127
+ });
128
+
129
+ export { items };
130
+ ```
131
+
132
+ ### Service Layer Pattern
133
+
134
+ Create services for business logic:
135
+
136
+ ```typescript
137
+ import { prisma } from '../config/database.js';
138
+ import { NotFoundError, ConflictError } from '../lib/errors.js';
139
+
140
+ export class ItemService {
141
+ static async getAll(page = 1, limit = 10) {
142
+ const skip = (page - 1) * limit;
143
+ const [items, total] = await Promise.all([
144
+ prisma.item.findMany({ skip, take: limit }),
145
+ prisma.item.count(),
146
+ ]);
147
+ return { items, total, page, limit };
148
+ }
149
+
150
+ static async getById(id: string) {
151
+ const item = await prisma.item.findUnique({ where: { id } });
152
+ if (!item) throw new NotFoundError(`Item ${id} not found`);
153
+ return item;
154
+ }
155
+
156
+ static async create(data: CreateItem) {
157
+ // Check for duplicates
158
+ const existing = await prisma.item.findUnique({ where: { name: data.name } });
159
+ if (existing) throw new ConflictError('Item already exists');
160
+
161
+ return prisma.item.create({ data });
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Environment Variables
167
+
168
+ All env vars are validated with Zod in `src/config/env.ts`. Add new variables:
169
+
170
+ ```typescript
171
+ const envSchema = z.object({
172
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
173
+ PORT: z.coerce.number().default(3001),
174
+ // Add your vars here
175
+ NEW_VAR: z.string().min(1),
176
+ });
177
+ ```
178
+
179
+ ### Rate Limiting
180
+
181
+ Apply rate limiting to routes:
182
+
183
+ ```typescript
184
+ import { rateLimits } from '../middleware/rate-limit.middleware.js';
185
+
186
+ // Use preset limits
187
+ app.use('/api/*', rateLimits.standard); // 100 req/min
188
+
189
+ // Custom limit
190
+ app.use('/auth/login', rateLimit({
191
+ windowMs: 15 * 60 * 1000, // 15 minutes
192
+ max: 5,
193
+ message: 'Too many login attempts',
194
+ }));
195
+ ```
196
+
197
+ ### Testing
198
+
199
+ Write tests for all routes and services:
200
+
201
+ ```typescript
202
+ import { describe, it, expect } from 'vitest';
203
+ import { Hono } from 'hono';
204
+ import { items } from '../items.route.js';
205
+
206
+ describe('Items Route', () => {
207
+ const app = new Hono();
208
+ app.route('/items', items);
209
+
210
+ it('should list items', async () => {
211
+ const res = await app.request('/items?page=1&limit=10');
212
+ expect(res.status).toBe(200);
213
+
214
+ const json = await res.json();
215
+ expect(json.success).toBe(true);
216
+ expect(json.data).toBeInstanceOf(Array);
217
+ expect(json.pagination).toBeDefined();
218
+ });
219
+ });
220
+ ```
221
+
222
+ ## Database (if enabled)
223
+
224
+ ### Prisma Commands
225
+
226
+ ```bash
227
+ pnpm prisma:generate # Generate Prisma client
228
+ pnpm prisma:migrate # Create and run migrations
229
+ pnpm prisma:studio # Open Prisma Studio UI
230
+ pnpm prisma:push # Push schema to database
231
+ ```
232
+
233
+ ### Creating Migrations
234
+
235
+ ```bash
236
+ # After editing schema.prisma
237
+ pnpm prisma:migrate
238
+ # Enter migration name when prompted
239
+ ```
240
+
241
+ ## Type Sharing
242
+
243
+ Import shared types from the shared package:
244
+
245
+ ```typescript
246
+ import { User, CreateUser, UpdateUser } from '@__PROJECT_NAME__/shared';
247
+ import { UserSchema } from '@__PROJECT_NAME__/shared';
248
+
249
+ // Use schemas for validation
250
+ const result = UserSchema.parse(data);
251
+
252
+ // Use types for type safety
253
+ const user: User = await getUser(id);
254
+ ```
255
+
256
+ ## Best Practices
257
+
258
+ ### DO:
259
+ - ✅ Use custom error classes, not raw `throw new Error()`
260
+ - ✅ Use response helpers for all routes
261
+ - ✅ Validate all input with Zod schemas
262
+ - ✅ Put business logic in services, not routes
263
+ - ✅ Write tests for new features
264
+ - ✅ Use type-safe imports from shared package
265
+ - ✅ Log important operations with structured logging
266
+ - ✅ Handle async operations properly with try/catch
267
+
268
+ ### DON'T:
269
+ - ❌ Don't return raw `c.json()` - use response helpers
270
+ - ❌ Don't put business logic in routes - use services
271
+ - ❌ Don't use `any` type - maintain type safety
272
+ - ❌ Don't skip validation - always validate input
273
+ - ❌ Don't commit `.env` files - use `.env.example`
274
+ - ❌ Don't use blocking operations in async handlers
275
+ - ❌ Don't modify shared types here - edit in `packages/shared`
276
+
277
+ ## Common Tasks
278
+
279
+ ### Adding a New Route
280
+
281
+ 1. Create route file in `src/routes/`
282
+ 2. Create service in `src/services/` (if needed)
283
+ 3. Add validation schemas in `packages/shared/src/schemas/`
284
+ 4. Register route in `src/index.ts`
285
+ 5. Write tests in `src/routes/__tests__/`
286
+
287
+ ### Adding Middleware
288
+
289
+ 1. Create in `src/middleware/`
290
+ 2. Export middleware function
291
+ 3. Apply in `src/index.ts` with `app.use()`
292
+
293
+ ### Adding Environment Variables
294
+
295
+ 1. Add to `.env.example`
296
+ 2. Add to `src/config/env.ts` schema
297
+ 3. Use via `env.YOUR_VAR`
298
+
299
+ ## Debugging
300
+
301
+ ```typescript
302
+ // Use structured logging
303
+ import { logger } from './config/logger.js';
304
+
305
+ logger.info('User logged in', { userId: user.id });
306
+ logger.warn('Rate limit approaching', { ip, count });
307
+ logger.error('Database error', { error, query });
308
+ ```
309
+
310
+ ## Performance Tips
311
+
312
+ - Use database indexes for frequently queried fields
313
+ - Use pagination for list endpoints
314
+ - Cache expensive operations with Redis (if enabled)
315
+ - Use `prisma.$transaction()` for multiple related operations
316
+ - Profile with `pnpm test:coverage` to find slow tests
317
+
318
+ ## Security Checklist
319
+
320
+ - [ ] All inputs are validated with Zod
321
+ - [ ] Rate limiting is applied to auth endpoints
322
+ - [ ] Sensitive data is not logged
323
+ - [ ] Database queries use parameterized queries (Prisma does this)
324
+ - [ ] CORS is configured correctly
325
+ - [ ] Environment variables are validated on startup
326
+ - [ ] Error messages don't leak sensitive info in production