agentic-team-templates 0.9.2 → 0.10.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/package.json +1 -1
- package/src/index.js +32 -0
- package/src/index.test.js +8 -0
- package/templates/javascript-expert/.cursorrules/language-deep-dive.md +245 -0
- package/templates/javascript-expert/.cursorrules/node-patterns.md +184 -0
- package/templates/javascript-expert/.cursorrules/overview.md +130 -0
- package/templates/javascript-expert/.cursorrules/performance.md +203 -0
- package/templates/javascript-expert/.cursorrules/react-patterns.md +249 -0
- package/templates/javascript-expert/.cursorrules/testing.md +403 -0
- package/templates/javascript-expert/.cursorrules/tooling.md +176 -0
- package/templates/javascript-expert/CLAUDE.md +448 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# JavaScript Testing
|
|
2
|
+
|
|
3
|
+
Expert-level testing patterns across all JavaScript environments.
|
|
4
|
+
|
|
5
|
+
## Testing Philosophy
|
|
6
|
+
|
|
7
|
+
- Tests are production code — same quality standards apply
|
|
8
|
+
- Test behavior and contracts, never implementation details
|
|
9
|
+
- Every bug fix starts with a failing test that reproduces the bug
|
|
10
|
+
- If it's hard to test, the design is wrong — refactor the code, not the test
|
|
11
|
+
|
|
12
|
+
## Test Structure
|
|
13
|
+
|
|
14
|
+
### Arrange-Act-Assert
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
describe('UserService', () => {
|
|
18
|
+
it('creates a user with hashed password', async () => {
|
|
19
|
+
// Arrange
|
|
20
|
+
const input = { email: 'test@example.com', password: 'secret123' };
|
|
21
|
+
const repo = createMockRepo();
|
|
22
|
+
|
|
23
|
+
// Act
|
|
24
|
+
const user = await createUser(repo, input);
|
|
25
|
+
|
|
26
|
+
// Assert
|
|
27
|
+
expect(user.email).toBe('test@example.com');
|
|
28
|
+
expect(user.passwordHash).not.toBe('secret123');
|
|
29
|
+
expect(await verify(user.passwordHash, 'secret123')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Descriptive Test Names
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Tests should read like specifications
|
|
38
|
+
describe('parseDate', () => {
|
|
39
|
+
it('parses ISO 8601 strings into Date objects', () => { ... });
|
|
40
|
+
it('returns null for invalid date strings', () => { ... });
|
|
41
|
+
it('handles timezone offsets correctly', () => { ... });
|
|
42
|
+
it('treats dates without timezone as UTC', () => { ... });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Not:
|
|
46
|
+
it('works', () => { ... });
|
|
47
|
+
it('test 1', () => { ... });
|
|
48
|
+
it('should work correctly', () => { ... });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Unit Testing
|
|
52
|
+
|
|
53
|
+
### Pure Functions
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { describe, it, expect } from 'vitest';
|
|
57
|
+
|
|
58
|
+
// Exhaustive edge case coverage
|
|
59
|
+
describe('clamp', () => {
|
|
60
|
+
it('returns value when within range', () => {
|
|
61
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns min when value is below range', () => {
|
|
65
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns max when value is above range', () => {
|
|
69
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('handles min equal to max', () => {
|
|
73
|
+
expect(clamp(5, 3, 3)).toBe(3);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('handles negative ranges', () => {
|
|
77
|
+
expect(clamp(0, -10, -5)).toBe(-5);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Parameterized Tests
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Use it.each for data-driven tests
|
|
86
|
+
describe('slugify', () => {
|
|
87
|
+
it.each([
|
|
88
|
+
['Hello World', 'hello-world'],
|
|
89
|
+
[' spaces ', 'spaces'],
|
|
90
|
+
['Special!@#Characters', 'specialcharacters'],
|
|
91
|
+
['already-slugified', 'already-slugified'],
|
|
92
|
+
['MiXeD CaSe', 'mixed-case'],
|
|
93
|
+
['', ''],
|
|
94
|
+
])('converts "%s" to "%s"', (input, expected) => {
|
|
95
|
+
expect(slugify(input)).toBe(expected);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Testing Async Code
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
describe('fetchUser', () => {
|
|
104
|
+
it('returns user data for valid ID', async () => {
|
|
105
|
+
const user = await fetchUser('123');
|
|
106
|
+
expect(user).toEqual({ id: '123', name: 'John' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns error result for missing user', async () => {
|
|
110
|
+
const result = await fetchUser('nonexistent');
|
|
111
|
+
expect(result.ok).toBe(false);
|
|
112
|
+
expect(result.error.code).toBe('NOT_FOUND');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('respects AbortSignal', async () => {
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
controller.abort();
|
|
118
|
+
|
|
119
|
+
await expect(
|
|
120
|
+
fetchUser('123', { signal: controller.signal })
|
|
121
|
+
).rejects.toThrow('aborted');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Integration Testing
|
|
127
|
+
|
|
128
|
+
### API Testing
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
132
|
+
import { createApp } from '../app.js';
|
|
133
|
+
|
|
134
|
+
describe('POST /api/users', () => {
|
|
135
|
+
let app: ReturnType<typeof createApp>;
|
|
136
|
+
|
|
137
|
+
beforeAll(async () => {
|
|
138
|
+
app = createApp({ db: createTestDb() });
|
|
139
|
+
await app.ready();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterAll(async () => {
|
|
143
|
+
await app.close();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('creates user and returns 201', async () => {
|
|
147
|
+
const response = await app.inject({
|
|
148
|
+
method: 'POST',
|
|
149
|
+
url: '/api/users',
|
|
150
|
+
payload: { email: 'new@example.com', name: 'New User' },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(response.statusCode).toBe(201);
|
|
154
|
+
expect(response.json()).toMatchObject({
|
|
155
|
+
email: 'new@example.com',
|
|
156
|
+
name: 'New User',
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('returns 400 for invalid email', async () => {
|
|
161
|
+
const response = await app.inject({
|
|
162
|
+
method: 'POST',
|
|
163
|
+
url: '/api/users',
|
|
164
|
+
payload: { email: 'not-an-email', name: 'Bad' },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(response.statusCode).toBe(400);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('returns 409 for duplicate email', async () => {
|
|
171
|
+
await app.inject({
|
|
172
|
+
method: 'POST',
|
|
173
|
+
url: '/api/users',
|
|
174
|
+
payload: { email: 'dupe@example.com', name: 'First' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const response = await app.inject({
|
|
178
|
+
method: 'POST',
|
|
179
|
+
url: '/api/users',
|
|
180
|
+
payload: { email: 'dupe@example.com', name: 'Second' },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(response.statusCode).toBe(409);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Database Testing
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// Use transactions for test isolation
|
|
192
|
+
describe('UserRepository', () => {
|
|
193
|
+
let db: TestDatabase;
|
|
194
|
+
|
|
195
|
+
beforeEach(async () => {
|
|
196
|
+
db = await createTestDatabase();
|
|
197
|
+
await db.beginTransaction();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
afterEach(async () => {
|
|
201
|
+
await db.rollbackTransaction();
|
|
202
|
+
await db.close();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('finds user by email', async () => {
|
|
206
|
+
await db.seed({ users: [{ email: 'test@example.com' }] });
|
|
207
|
+
const repo = new UserRepository(db);
|
|
208
|
+
|
|
209
|
+
const user = await repo.findByEmail('test@example.com');
|
|
210
|
+
|
|
211
|
+
expect(user).not.toBeNull();
|
|
212
|
+
expect(user!.email).toBe('test@example.com');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## React Component Testing
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { render, screen, within } from '@testing-library/react';
|
|
221
|
+
import userEvent from '@testing-library/user-event';
|
|
222
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
223
|
+
|
|
224
|
+
describe('TodoList', () => {
|
|
225
|
+
it('adds a new todo when form is submitted', async () => {
|
|
226
|
+
const user = userEvent.setup();
|
|
227
|
+
render(<TodoList />);
|
|
228
|
+
|
|
229
|
+
await user.type(screen.getByRole('textbox', { name: /new todo/i }), 'Buy milk');
|
|
230
|
+
await user.click(screen.getByRole('button', { name: /add/i }));
|
|
231
|
+
|
|
232
|
+
expect(screen.getByText('Buy milk')).toBeInTheDocument();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('marks todo as complete when checkbox is clicked', async () => {
|
|
236
|
+
const user = userEvent.setup();
|
|
237
|
+
render(<TodoList initialTodos={[{ id: '1', text: 'Test', done: false }]} />);
|
|
238
|
+
|
|
239
|
+
await user.click(screen.getByRole('checkbox', { name: /test/i }));
|
|
240
|
+
|
|
241
|
+
expect(screen.getByRole('checkbox', { name: /test/i })).toBeChecked();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('filters completed todos', async () => {
|
|
245
|
+
const user = userEvent.setup();
|
|
246
|
+
render(
|
|
247
|
+
<TodoList initialTodos={[
|
|
248
|
+
{ id: '1', text: 'Done', done: true },
|
|
249
|
+
{ id: '2', text: 'Not done', done: false },
|
|
250
|
+
]} />
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
await user.click(screen.getByRole('button', { name: /active/i }));
|
|
254
|
+
|
|
255
|
+
expect(screen.queryByText('Done')).not.toBeInTheDocument();
|
|
256
|
+
expect(screen.getByText('Not done')).toBeInTheDocument();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Mocking
|
|
262
|
+
|
|
263
|
+
### Strategic Mocking
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Mock at boundaries, not internals
|
|
267
|
+
// Good: Mock the HTTP client, not internal functions
|
|
268
|
+
const mockFetch = vi.fn();
|
|
269
|
+
|
|
270
|
+
// Bad: Mocking private methods or internal state
|
|
271
|
+
// If you need to mock internals, the design needs work
|
|
272
|
+
|
|
273
|
+
// Use dependency injection for testability
|
|
274
|
+
interface EmailSender {
|
|
275
|
+
send(to: string, subject: string, body: string): Promise<void>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Production
|
|
279
|
+
const smtpSender: EmailSender = { send: async (...args) => { /* SMTP */ } };
|
|
280
|
+
|
|
281
|
+
// Test
|
|
282
|
+
const mockSender: EmailSender = { send: vi.fn() };
|
|
283
|
+
|
|
284
|
+
const service = new NotificationService(mockSender);
|
|
285
|
+
await service.notifyUser(user, 'Welcome');
|
|
286
|
+
expect(mockSender.send).toHaveBeenCalledWith(user.email, 'Welcome', expect.any(String));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### MSW for API Mocking
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { http, HttpResponse } from 'msw';
|
|
293
|
+
import { setupServer } from 'msw/node';
|
|
294
|
+
|
|
295
|
+
const handlers = [
|
|
296
|
+
http.get('/api/users/:id', ({ params }) => {
|
|
297
|
+
return HttpResponse.json({ id: params.id, name: 'Test User' });
|
|
298
|
+
}),
|
|
299
|
+
http.post('/api/users', async ({ request }) => {
|
|
300
|
+
const body = await request.json();
|
|
301
|
+
return HttpResponse.json(body, { status: 201 });
|
|
302
|
+
}),
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
const server = setupServer(...handlers);
|
|
306
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
|
|
307
|
+
afterEach(() => server.resetHandlers());
|
|
308
|
+
afterAll(() => server.close());
|
|
309
|
+
|
|
310
|
+
// Override for specific test
|
|
311
|
+
it('handles server errors', async () => {
|
|
312
|
+
server.use(
|
|
313
|
+
http.get('/api/users/:id', () => {
|
|
314
|
+
return new HttpResponse(null, { status: 500 });
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const result = await fetchUser('123');
|
|
319
|
+
expect(result.ok).toBe(false);
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## E2E Testing
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { test, expect } from '@playwright/test';
|
|
327
|
+
|
|
328
|
+
test.describe('Authentication flow', () => {
|
|
329
|
+
test('login, perform action, logout', async ({ page }) => {
|
|
330
|
+
await page.goto('/login');
|
|
331
|
+
|
|
332
|
+
await page.getByLabel('Email').fill('user@example.com');
|
|
333
|
+
await page.getByLabel('Password').fill('password');
|
|
334
|
+
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
335
|
+
|
|
336
|
+
await expect(page).toHaveURL('/dashboard');
|
|
337
|
+
await expect(page.getByText('Welcome')).toBeVisible();
|
|
338
|
+
|
|
339
|
+
await page.getByRole('button', { name: 'Sign out' }).click();
|
|
340
|
+
await expect(page).toHaveURL('/login');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Visual regression
|
|
345
|
+
test('homepage matches snapshot', async ({ page }) => {
|
|
346
|
+
await page.goto('/');
|
|
347
|
+
await expect(page).toHaveScreenshot('homepage.png', {
|
|
348
|
+
maxDiffPixelRatio: 0.01,
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Test Anti-Patterns
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// Never: Testing implementation details
|
|
357
|
+
expect(component.instance().state.isLoading).toBe(true); // BAD
|
|
358
|
+
|
|
359
|
+
// Never: Snapshot testing for logic (only for visual regression)
|
|
360
|
+
expect(complexObject).toMatchSnapshot(); // BAD — fragile, meaningless diffs
|
|
361
|
+
|
|
362
|
+
// Never: Sleeping instead of waiting
|
|
363
|
+
await new Promise(r => setTimeout(r, 1000)); // BAD
|
|
364
|
+
await waitFor(() => expect(screen.getByText('loaded')).toBeVisible()); // GOOD
|
|
365
|
+
|
|
366
|
+
// Never: Tests that depend on execution order
|
|
367
|
+
// Each test must be independently runnable
|
|
368
|
+
|
|
369
|
+
// Never: Ignoring flaky tests
|
|
370
|
+
// A flaky test is a bug — either in the test or the code. Fix it.
|
|
371
|
+
|
|
372
|
+
// Never: Testing third-party library behavior
|
|
373
|
+
// Trust your dependencies. Test YOUR code's integration with them.
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Coverage
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// vitest.config.ts
|
|
380
|
+
export default defineConfig({
|
|
381
|
+
test: {
|
|
382
|
+
coverage: {
|
|
383
|
+
provider: 'v8',
|
|
384
|
+
thresholds: {
|
|
385
|
+
statements: 80,
|
|
386
|
+
branches: 80,
|
|
387
|
+
functions: 80,
|
|
388
|
+
lines: 80,
|
|
389
|
+
},
|
|
390
|
+
exclude: [
|
|
391
|
+
'node_modules/',
|
|
392
|
+
'test/',
|
|
393
|
+
'**/*.d.ts',
|
|
394
|
+
'**/*.config.*',
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Coverage is a metric, not a goal
|
|
401
|
+
// 100% coverage with bad tests is worse than 70% coverage with great tests
|
|
402
|
+
// Focus on testing behavior and edge cases, not hitting line counts
|
|
403
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# JavaScript Tooling
|
|
2
|
+
|
|
3
|
+
Modern JavaScript tooling configuration and best practices.
|
|
4
|
+
|
|
5
|
+
## TypeScript Configuration
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
// tsconfig.json — strict everything
|
|
9
|
+
{
|
|
10
|
+
"compilerOptions": {
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"noUnusedLocals": true,
|
|
14
|
+
"noUnusedParameters": true,
|
|
15
|
+
"exactOptionalPropertyTypes": true,
|
|
16
|
+
"noImplicitReturns": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"isolatedModules": true,
|
|
20
|
+
"verbatimModuleSyntax": true,
|
|
21
|
+
"moduleResolution": "bundler",
|
|
22
|
+
"module": "ESNext",
|
|
23
|
+
"target": "ES2022",
|
|
24
|
+
"lib": ["ES2023"],
|
|
25
|
+
"skipLibCheck": true,
|
|
26
|
+
"declaration": true,
|
|
27
|
+
"declarationMap": true,
|
|
28
|
+
"sourceMap": true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Package Management
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
// package.json essentials
|
|
37
|
+
{
|
|
38
|
+
"type": "module",
|
|
39
|
+
"engines": { "node": ">=20" },
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"import": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"files": ["dist"],
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Dependency Hygiene
|
|
52
|
+
|
|
53
|
+
- Pin exact versions for applications (`"react": "19.0.0"`)
|
|
54
|
+
- Use ranges for libraries (`"react": "^19.0.0"`)
|
|
55
|
+
- Audit dependencies regularly (`npm audit`)
|
|
56
|
+
- Prefer packages with zero dependencies
|
|
57
|
+
- Check bundle size impact before adding (`bundlephobia.com`)
|
|
58
|
+
- Remove unused dependencies (`npx depcheck`)
|
|
59
|
+
|
|
60
|
+
## Linting
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
// eslint.config.js (flat config)
|
|
64
|
+
import tseslint from 'typescript-eslint';
|
|
65
|
+
|
|
66
|
+
export default tseslint.config(
|
|
67
|
+
...tseslint.configs.strictTypeChecked,
|
|
68
|
+
{
|
|
69
|
+
rules: {
|
|
70
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
71
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
72
|
+
'@typescript-eslint/no-misused-promises': 'error',
|
|
73
|
+
'@typescript-eslint/prefer-readonly': 'error',
|
|
74
|
+
'@typescript-eslint/strict-boolean-expressions': 'error',
|
|
75
|
+
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Build Tools
|
|
82
|
+
|
|
83
|
+
### Vite (Frontend)
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// vite.config.ts
|
|
87
|
+
import { defineConfig } from 'vite';
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
build: {
|
|
91
|
+
target: 'es2022',
|
|
92
|
+
sourcemap: true,
|
|
93
|
+
rollupOptions: {
|
|
94
|
+
output: {
|
|
95
|
+
manualChunks: {
|
|
96
|
+
vendor: ['react', 'react-dom'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### tsup (Libraries)
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// tsup.config.ts
|
|
108
|
+
import { defineConfig } from 'tsup';
|
|
109
|
+
|
|
110
|
+
export default defineConfig({
|
|
111
|
+
entry: ['src/index.ts'],
|
|
112
|
+
format: ['esm'],
|
|
113
|
+
dts: true,
|
|
114
|
+
sourcemap: true,
|
|
115
|
+
clean: true,
|
|
116
|
+
target: 'es2022',
|
|
117
|
+
splitting: true,
|
|
118
|
+
treeshake: true,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Git Hooks
|
|
123
|
+
|
|
124
|
+
```jsonc
|
|
125
|
+
// package.json
|
|
126
|
+
{
|
|
127
|
+
"scripts": {
|
|
128
|
+
"prepare": "husky",
|
|
129
|
+
"lint": "eslint .",
|
|
130
|
+
"typecheck": "tsc --noEmit",
|
|
131
|
+
"test": "vitest run",
|
|
132
|
+
"check": "npm run typecheck && npm run lint && npm run test"
|
|
133
|
+
},
|
|
134
|
+
"lint-staged": {
|
|
135
|
+
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
136
|
+
"*.{json,md,yml}": ["prettier --write"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Debugging
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Node.js debugger
|
|
145
|
+
// node --inspect-brk app.js
|
|
146
|
+
// Then connect Chrome DevTools to chrome://inspect
|
|
147
|
+
|
|
148
|
+
// Conditional breakpoints in code
|
|
149
|
+
if (suspiciousValue > threshold) {
|
|
150
|
+
debugger; // Only hits when condition is true
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Structured logging over console.log
|
|
154
|
+
import { createLogger } from './logger.js';
|
|
155
|
+
const log = createLogger('user-service');
|
|
156
|
+
|
|
157
|
+
log.info('User created', { userId: user.id, email: user.email });
|
|
158
|
+
log.error('Failed to create user', { error, input });
|
|
159
|
+
// Never log passwords, tokens, or PII
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Editor Configuration
|
|
163
|
+
|
|
164
|
+
```jsonc
|
|
165
|
+
// .vscode/settings.json (also works in Cursor)
|
|
166
|
+
{
|
|
167
|
+
"editor.formatOnSave": true,
|
|
168
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
169
|
+
"editor.codeActionsOnSave": {
|
|
170
|
+
"source.fixAll.eslint": "explicit",
|
|
171
|
+
"source.organizeImports": "explicit"
|
|
172
|
+
},
|
|
173
|
+
"typescript.preferences.importModuleSpecifierEnding": "js",
|
|
174
|
+
"typescript.tsdk": "node_modules/typescript/lib"
|
|
175
|
+
}
|
|
176
|
+
```
|