@malamute/ai-rules 1.0.0 → 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/README.md +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.tsx"
|
|
5
|
+
- "**/*.js"
|
|
6
|
+
- "**/*.py"
|
|
7
|
+
- "**/*.cs"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Software Engineering Principles
|
|
11
|
+
|
|
12
|
+
## YAGNI - You Aren't Gonna Need It
|
|
13
|
+
|
|
14
|
+
Don't implement features until they're actually needed.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// BAD - Building for hypothetical future
|
|
18
|
+
interface UserService {
|
|
19
|
+
getUser(id: string): User;
|
|
20
|
+
getUserWithCache(id: string, ttl?: number): User;
|
|
21
|
+
getUserAsync(id: string): Promise<User>;
|
|
22
|
+
getUserBatch(ids: string[]): User[];
|
|
23
|
+
getUserByEmail(email: string): User;
|
|
24
|
+
getUserByPhone(phone: string): User; // No one asked for this
|
|
25
|
+
getUserBySSN(ssn: string): User; // No one asked for this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// GOOD - Only what's needed now
|
|
29
|
+
interface UserService {
|
|
30
|
+
getUser(id: string): Promise<User>;
|
|
31
|
+
getUserByEmail(email: string): Promise<User>;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// BAD - Premature abstraction
|
|
37
|
+
class AbstractDataProcessor<T, R, C extends Config> {
|
|
38
|
+
// 200 lines of "flexible" code no one uses
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// GOOD - Concrete implementation
|
|
42
|
+
function processUserData(users: User[]): ProcessedUser[] {
|
|
43
|
+
return users.map(u => ({ ...u, fullName: `${u.first} ${u.last}` }));
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## KISS - Keep It Simple, Stupid
|
|
48
|
+
|
|
49
|
+
Choose the simplest solution that works.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// BAD - Over-engineered
|
|
53
|
+
class StringUtils {
|
|
54
|
+
private static instance: StringUtils;
|
|
55
|
+
private constructor() {}
|
|
56
|
+
|
|
57
|
+
static getInstance(): StringUtils {
|
|
58
|
+
if (!this.instance) this.instance = new StringUtils();
|
|
59
|
+
return this.instance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
capitalize(str: string): string {
|
|
63
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Usage: StringUtils.getInstance().capitalize('hello')
|
|
67
|
+
|
|
68
|
+
// GOOD - Simple function
|
|
69
|
+
function capitalize(str: string): string {
|
|
70
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
71
|
+
}
|
|
72
|
+
// Usage: capitalize('hello')
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// BAD - Unnecessary complexity
|
|
77
|
+
const isAdult = (age: number) =>
|
|
78
|
+
new AgeValidator(new AgePolicy(18)).validate(age).isValid();
|
|
79
|
+
|
|
80
|
+
// GOOD - Direct and clear
|
|
81
|
+
const isAdult = (age: number) => age >= 18;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## SOLID Principles
|
|
85
|
+
|
|
86
|
+
### S - Single Responsibility
|
|
87
|
+
|
|
88
|
+
A class/function should have only one reason to change.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// BAD - Multiple responsibilities
|
|
92
|
+
class UserService {
|
|
93
|
+
createUser(data: UserData) { /* ... */ }
|
|
94
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
95
|
+
generatePDF(user: User) { /* ... */ }
|
|
96
|
+
validateCreditCard(card: Card) { /* ... */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// GOOD - Single responsibility each
|
|
100
|
+
class UserService {
|
|
101
|
+
createUser(data: UserData) { /* ... */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class EmailService {
|
|
105
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class PDFService {
|
|
109
|
+
generateUserReport(user: User) { /* ... */ }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### O - Open/Closed
|
|
114
|
+
|
|
115
|
+
Open for extension, closed for modification.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// BAD - Must modify to add new types
|
|
119
|
+
function calculateArea(shape: Shape) {
|
|
120
|
+
if (shape.type === 'circle') {
|
|
121
|
+
return Math.PI * shape.radius ** 2;
|
|
122
|
+
} else if (shape.type === 'rectangle') {
|
|
123
|
+
return shape.width * shape.height;
|
|
124
|
+
}
|
|
125
|
+
// Must add new else-if for each shape
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// GOOD - Extend without modifying
|
|
129
|
+
interface Shape {
|
|
130
|
+
area(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Circle implements Shape {
|
|
134
|
+
constructor(private radius: number) {}
|
|
135
|
+
area() { return Math.PI * this.radius ** 2; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class Rectangle implements Shape {
|
|
139
|
+
constructor(private width: number, private height: number) {}
|
|
140
|
+
area() { return this.width * this.height; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add new shapes without changing existing code
|
|
144
|
+
class Triangle implements Shape {
|
|
145
|
+
constructor(private base: number, private height: number) {}
|
|
146
|
+
area() { return 0.5 * this.base * this.height; }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### L - Liskov Substitution
|
|
151
|
+
|
|
152
|
+
Subtypes must be substitutable for their base types.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// BAD - Violates LSP
|
|
156
|
+
class Bird {
|
|
157
|
+
fly() { /* ... */ }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class Penguin extends Bird {
|
|
161
|
+
fly() { throw new Error("Can't fly!"); } // Breaks substitutability
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// GOOD - Proper hierarchy
|
|
165
|
+
interface Bird {
|
|
166
|
+
move(): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class FlyingBird implements Bird {
|
|
170
|
+
move() { this.fly(); }
|
|
171
|
+
private fly() { /* ... */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class Penguin implements Bird {
|
|
175
|
+
move() { this.swim(); }
|
|
176
|
+
private swim() { /* ... */ }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### I - Interface Segregation
|
|
181
|
+
|
|
182
|
+
Clients shouldn't depend on interfaces they don't use.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// BAD - Fat interface
|
|
186
|
+
interface Worker {
|
|
187
|
+
work(): void;
|
|
188
|
+
eat(): void;
|
|
189
|
+
sleep(): void;
|
|
190
|
+
attendMeeting(): void;
|
|
191
|
+
writeReport(): void;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// GOOD - Segregated interfaces
|
|
195
|
+
interface Workable {
|
|
196
|
+
work(): void;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface Meetable {
|
|
200
|
+
attendMeeting(): void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface Reportable {
|
|
204
|
+
writeReport(): void;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class Developer implements Workable, Meetable {
|
|
208
|
+
work() { /* ... */ }
|
|
209
|
+
attendMeeting() { /* ... */ }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class Robot implements Workable {
|
|
213
|
+
work() { /* ... */ }
|
|
214
|
+
// Doesn't need eat, sleep, or meetings
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### D - Dependency Inversion
|
|
219
|
+
|
|
220
|
+
Depend on abstractions, not concretions.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// BAD - Depends on concrete implementation
|
|
224
|
+
class OrderService {
|
|
225
|
+
private db = new MySQLDatabase();
|
|
226
|
+
private mailer = new SendGridMailer();
|
|
227
|
+
|
|
228
|
+
createOrder(order: Order) {
|
|
229
|
+
this.db.save(order);
|
|
230
|
+
this.mailer.send(order.userEmail, 'Order confirmed');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// GOOD - Depends on abstractions
|
|
235
|
+
class OrderService {
|
|
236
|
+
constructor(
|
|
237
|
+
private repository: OrderRepository,
|
|
238
|
+
private notifier: Notifier,
|
|
239
|
+
) {}
|
|
240
|
+
|
|
241
|
+
createOrder(order: Order) {
|
|
242
|
+
this.repository.save(order);
|
|
243
|
+
this.notifier.notify(order.userEmail, 'Order confirmed');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Can inject any implementation
|
|
248
|
+
new OrderService(new PostgresOrderRepo(), new EmailNotifier());
|
|
249
|
+
new OrderService(new MongoOrderRepo(), new SMSNotifier());
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## SoC - Separation of Concerns
|
|
253
|
+
|
|
254
|
+
Separate code into distinct sections, each handling a specific concern.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// BAD - Mixed concerns
|
|
258
|
+
async function handleUserRegistration(req: Request, res: Response) {
|
|
259
|
+
// Validation
|
|
260
|
+
if (!req.body.email.includes('@')) {
|
|
261
|
+
return res.status(400).json({ error: 'Invalid email' });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Business logic
|
|
265
|
+
const hashedPassword = await bcrypt.hash(req.body.password, 10);
|
|
266
|
+
|
|
267
|
+
// Data access
|
|
268
|
+
const user = await db.query(
|
|
269
|
+
'INSERT INTO users (email, password) VALUES ($1, $2)',
|
|
270
|
+
[req.body.email, hashedPassword]
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Presentation
|
|
274
|
+
return res.json({ id: user.id, email: user.email });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// GOOD - Separated concerns
|
|
278
|
+
// validation.ts
|
|
279
|
+
const userSchema = z.object({
|
|
280
|
+
email: z.string().email(),
|
|
281
|
+
password: z.string().min(8),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// user.service.ts
|
|
285
|
+
class UserService {
|
|
286
|
+
async register(data: CreateUserDto): Promise<User> {
|
|
287
|
+
const hashedPassword = await this.hashPassword(data.password);
|
|
288
|
+
return this.userRepository.create({ ...data, password: hashedPassword });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// user.controller.ts
|
|
293
|
+
async function register(req: Request, res: Response) {
|
|
294
|
+
const data = userSchema.parse(req.body);
|
|
295
|
+
const user = await userService.register(data);
|
|
296
|
+
return res.json(toUserResponse(user));
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## DRY - Don't Repeat Yourself
|
|
301
|
+
|
|
302
|
+
But don't over-apply it. Duplication is better than the wrong abstraction.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// BAD - Premature DRY (wrong abstraction)
|
|
306
|
+
function processEntity(entity: User | Product | Order, action: string) {
|
|
307
|
+
// 100 lines of if/else handling all cases
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// GOOD - Some duplication is OK when contexts differ
|
|
311
|
+
function processUser(user: User) { /* user-specific logic */ }
|
|
312
|
+
function processProduct(product: Product) { /* product-specific logic */ }
|
|
313
|
+
function processOrder(order: Order) { /* order-specific logic */ }
|
|
314
|
+
|
|
315
|
+
// GOOD - DRY when truly duplicated
|
|
316
|
+
function formatCurrency(amount: number): string {
|
|
317
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
|
318
|
+
}
|
|
319
|
+
// Use everywhere instead of repeating the formatting logic
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Summary
|
|
323
|
+
|
|
324
|
+
| Principle | Remember |
|
|
325
|
+
|-----------|----------|
|
|
326
|
+
| YAGNI | Build what you need now, not what you might need |
|
|
327
|
+
| KISS | Simple > Clever. If it's hard to explain, simplify it |
|
|
328
|
+
| SRP | One reason to change per class/function |
|
|
329
|
+
| OCP | Add new code, don't modify existing code |
|
|
330
|
+
| LSP | Subtypes must honor parent contracts |
|
|
331
|
+
| ISP | Small, focused interfaces > large, general ones |
|
|
332
|
+
| DIP | Inject dependencies, don't instantiate them |
|
|
333
|
+
| SoC | Validation, business logic, data access = separate |
|
|
334
|
+
| DRY | Avoid duplication, but not at the cost of clarity |
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- ".github/workflows/**"
|
|
4
|
+
- ".gitlab-ci.yml"
|
|
5
|
+
- "azure-pipelines.yml"
|
|
6
|
+
- "Jenkinsfile"
|
|
7
|
+
- ".circleci/**"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CI/CD Best Practices
|
|
11
|
+
|
|
12
|
+
## Pipeline Principles
|
|
13
|
+
|
|
14
|
+
- **Fast feedback**: Run quick checks first (lint, type-check)
|
|
15
|
+
- **Fail fast**: Stop pipeline on first failure
|
|
16
|
+
- **Parallelization**: Run independent jobs in parallel
|
|
17
|
+
- **Caching**: Cache dependencies between runs
|
|
18
|
+
- **Artifacts**: Pass build outputs between jobs
|
|
19
|
+
|
|
20
|
+
## GitHub Actions
|
|
21
|
+
|
|
22
|
+
### Basic Pipeline
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# .github/workflows/ci.yml
|
|
26
|
+
name: CI
|
|
27
|
+
|
|
28
|
+
on:
|
|
29
|
+
push:
|
|
30
|
+
branches: [main, develop]
|
|
31
|
+
pull_request:
|
|
32
|
+
branches: [main]
|
|
33
|
+
|
|
34
|
+
concurrency:
|
|
35
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
36
|
+
cancel-in-progress: true
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
lint:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: '20'
|
|
46
|
+
cache: 'npm'
|
|
47
|
+
- run: npm ci
|
|
48
|
+
- run: npm run lint
|
|
49
|
+
|
|
50
|
+
test:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
needs: lint
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: '20'
|
|
58
|
+
cache: 'npm'
|
|
59
|
+
- run: npm ci
|
|
60
|
+
- run: npm test -- --coverage
|
|
61
|
+
- uses: codecov/codecov-action@v3
|
|
62
|
+
|
|
63
|
+
build:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
needs: test
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- uses: actions/setup-node@v4
|
|
69
|
+
with:
|
|
70
|
+
node-version: '20'
|
|
71
|
+
cache: 'npm'
|
|
72
|
+
- run: npm ci
|
|
73
|
+
- run: npm run build
|
|
74
|
+
- uses: actions/upload-artifact@v4
|
|
75
|
+
with:
|
|
76
|
+
name: build
|
|
77
|
+
path: dist/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Nx Monorepo Pipeline
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
name: CI (Nx)
|
|
84
|
+
|
|
85
|
+
on:
|
|
86
|
+
push:
|
|
87
|
+
branches: [main]
|
|
88
|
+
pull_request:
|
|
89
|
+
|
|
90
|
+
jobs:
|
|
91
|
+
main:
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
with:
|
|
96
|
+
fetch-depth: 0
|
|
97
|
+
|
|
98
|
+
- uses: actions/setup-node@v4
|
|
99
|
+
with:
|
|
100
|
+
node-version: '20'
|
|
101
|
+
cache: 'npm'
|
|
102
|
+
|
|
103
|
+
- run: npm ci
|
|
104
|
+
|
|
105
|
+
- uses: nrwl/nx-set-shas@v4
|
|
106
|
+
|
|
107
|
+
# Run affected commands
|
|
108
|
+
- run: npx nx affected -t lint --parallel=3
|
|
109
|
+
- run: npx nx affected -t test --parallel=3 --coverage
|
|
110
|
+
- run: npx nx affected -t build --parallel=3
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Deploy Pipeline
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
# .github/workflows/deploy.yml
|
|
117
|
+
name: Deploy
|
|
118
|
+
|
|
119
|
+
on:
|
|
120
|
+
push:
|
|
121
|
+
branches: [main]
|
|
122
|
+
|
|
123
|
+
jobs:
|
|
124
|
+
deploy:
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
environment: production
|
|
127
|
+
steps:
|
|
128
|
+
- uses: actions/checkout@v4
|
|
129
|
+
|
|
130
|
+
- name: Build Docker image
|
|
131
|
+
run: |
|
|
132
|
+
docker build -t myapp:${{ github.sha }} .
|
|
133
|
+
|
|
134
|
+
- name: Login to Registry
|
|
135
|
+
uses: docker/login-action@v3
|
|
136
|
+
with:
|
|
137
|
+
registry: ghcr.io
|
|
138
|
+
username: ${{ github.actor }}
|
|
139
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
140
|
+
|
|
141
|
+
- name: Push image
|
|
142
|
+
run: |
|
|
143
|
+
docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}
|
|
144
|
+
docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
|
|
145
|
+
docker push ghcr.io/${{ github.repository }} --all-tags
|
|
146
|
+
|
|
147
|
+
- name: Deploy to production
|
|
148
|
+
run: |
|
|
149
|
+
# kubectl, ssh, or cloud CLI deployment
|
|
150
|
+
echo "Deploying ${{ github.sha }}"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Branch Protection
|
|
154
|
+
|
|
155
|
+
Configure in GitHub Settings:
|
|
156
|
+
|
|
157
|
+
- Require PR reviews before merging
|
|
158
|
+
- Require status checks (CI must pass)
|
|
159
|
+
- Require branches to be up to date
|
|
160
|
+
- Enforce linear history (squash or rebase)
|
|
161
|
+
- Restrict force pushes
|
|
162
|
+
|
|
163
|
+
## Secrets Management
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
# Reference secrets (never hardcode!)
|
|
167
|
+
env:
|
|
168
|
+
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
169
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
170
|
+
|
|
171
|
+
# Use environments for different configs
|
|
172
|
+
jobs:
|
|
173
|
+
deploy:
|
|
174
|
+
environment: production # Uses production secrets
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Caching Strategies
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
# Node.js
|
|
181
|
+
- uses: actions/setup-node@v4
|
|
182
|
+
with:
|
|
183
|
+
cache: 'npm' # Built-in caching
|
|
184
|
+
|
|
185
|
+
# Custom cache
|
|
186
|
+
- uses: actions/cache@v4
|
|
187
|
+
with:
|
|
188
|
+
path: |
|
|
189
|
+
~/.npm
|
|
190
|
+
node_modules
|
|
191
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
192
|
+
restore-keys: |
|
|
193
|
+
${{ runner.os }}-node-
|
|
194
|
+
|
|
195
|
+
# Docker layer caching
|
|
196
|
+
- uses: docker/build-push-action@v5
|
|
197
|
+
with:
|
|
198
|
+
cache-from: type=gha
|
|
199
|
+
cache-to: type=gha,mode=max
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Matrix Builds
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
jobs:
|
|
206
|
+
test:
|
|
207
|
+
strategy:
|
|
208
|
+
matrix:
|
|
209
|
+
node: [18, 20, 22]
|
|
210
|
+
os: [ubuntu-latest, windows-latest]
|
|
211
|
+
runs-on: ${{ matrix.os }}
|
|
212
|
+
steps:
|
|
213
|
+
- uses: actions/setup-node@v4
|
|
214
|
+
with:
|
|
215
|
+
node-version: ${{ matrix.node }}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Release Automation
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
# .github/workflows/release.yml
|
|
222
|
+
name: Release
|
|
223
|
+
|
|
224
|
+
on:
|
|
225
|
+
push:
|
|
226
|
+
tags:
|
|
227
|
+
- 'v*'
|
|
228
|
+
|
|
229
|
+
jobs:
|
|
230
|
+
release:
|
|
231
|
+
runs-on: ubuntu-latest
|
|
232
|
+
steps:
|
|
233
|
+
- uses: actions/checkout@v4
|
|
234
|
+
|
|
235
|
+
- name: Create Release
|
|
236
|
+
uses: softprops/action-gh-release@v1
|
|
237
|
+
with:
|
|
238
|
+
generate_release_notes: true
|
|
239
|
+
files: |
|
|
240
|
+
dist/*.zip
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Quality Gates
|
|
244
|
+
|
|
245
|
+
| Check | Tool | Threshold |
|
|
246
|
+
|-------|------|-----------|
|
|
247
|
+
| Lint | ESLint/Ruff | 0 errors |
|
|
248
|
+
| Type check | TypeScript/mypy | 0 errors |
|
|
249
|
+
| Unit tests | Jest/pytest | 100% pass |
|
|
250
|
+
| Coverage | Codecov | ≥80% |
|
|
251
|
+
| Security | Snyk/Dependabot | 0 critical |
|
|
252
|
+
| Build | Framework CLI | Success |
|
|
253
|
+
|
|
254
|
+
## Anti-patterns
|
|
255
|
+
|
|
256
|
+
- Running all tests on every commit (use affected)
|
|
257
|
+
- Not caching dependencies
|
|
258
|
+
- Hardcoding secrets
|
|
259
|
+
- Not using concurrency limits
|
|
260
|
+
- Deploying without approval gates
|
|
261
|
+
- Missing rollback strategy
|
|
262
|
+
- No artifact retention policy
|