@etus/bhono-app 0.1.6 โ†’ 0.1.7

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.
Files changed (61) hide show
  1. package/package.json +3 -2
  2. package/templates/base/.claude/commands/check-skill-rules.md +112 -29
  3. package/templates/base/.claude/commands/linear/implement-issue.md +383 -55
  4. package/templates/base/.claude/commands/ship.md +77 -13
  5. package/templates/base/.claude/hooks/package-lock.json +0 -419
  6. package/templates/base/.claude/hooks/skill-activation-prompt.ts +185 -113
  7. package/templates/base/.claude/hooks/skill-tool-guard.sh +6 -0
  8. package/templates/base/.claude/hooks/skill-tool-guard.ts +198 -0
  9. package/templates/base/.claude/scripts/validate-skill-rules.sh +55 -32
  10. package/templates/base/.claude/settings.json +18 -11
  11. package/templates/base/.claude/skills/skill-rules.json +326 -173
  12. package/templates/base/.env.example +3 -0
  13. package/templates/base/README.md +9 -7
  14. package/templates/base/config/eslint.config.js +1 -0
  15. package/templates/base/config/wrangler.json +16 -17
  16. package/templates/base/docs/SETUP-GUIDE.md +566 -0
  17. package/templates/base/docs/architecture/README.md +162 -8
  18. package/templates/base/docs/architecture/api-catalog.md +575 -0
  19. package/templates/base/docs/architecture/c4-component.md +309 -0
  20. package/templates/base/docs/architecture/c4-container.md +183 -0
  21. package/templates/base/docs/architecture/c4-context.md +106 -0
  22. package/templates/base/docs/architecture/dependencies.md +327 -0
  23. package/templates/base/docs/architecture/tech-debt.md +184 -0
  24. package/templates/base/package.json +20 -15
  25. package/templates/base/scripts/capture-prod-session.ts +2 -2
  26. package/templates/base/scripts/sync-template.sh +104 -0
  27. package/templates/base/src/server/db/sql.ts +24 -4
  28. package/templates/base/src/server/index.ts +1 -0
  29. package/templates/base/src/server/lib/audited-db.ts +10 -10
  30. package/templates/base/src/server/middleware/account.ts +1 -1
  31. package/templates/base/src/server/middleware/auth.ts +11 -11
  32. package/templates/base/src/server/middleware/rate-limit.ts +3 -6
  33. package/templates/base/src/server/routes/auth/handlers.ts +5 -5
  34. package/templates/base/src/server/routes/auth/test-login.ts +9 -9
  35. package/templates/base/src/server/routes/index.ts +9 -0
  36. package/templates/base/src/server/routes/invitations/handlers.ts +6 -6
  37. package/templates/base/src/server/routes/openapi.ts +1 -1
  38. package/templates/base/src/server/services/accounts.ts +9 -9
  39. package/templates/base/src/server/services/audits.ts +12 -12
  40. package/templates/base/src/server/services/auth.ts +15 -15
  41. package/templates/base/src/server/services/invitations.ts +16 -16
  42. package/templates/base/src/server/services/users.ts +13 -13
  43. package/templates/base/src/shared/types/api.ts +66 -198
  44. package/templates/base/tests/e2e/auth.setup.ts +1 -1
  45. package/templates/base/tests/unit/server/auth/guards.test.ts +1 -1
  46. package/templates/base/tests/unit/server/middleware/auth.test.ts +273 -0
  47. package/templates/base/tests/unit/server/routes/auth/handlers.test.ts +111 -0
  48. package/templates/base/tests/unit/server/routes/users/handlers.test.ts +69 -5
  49. package/templates/base/tests/unit/server/services/accounts.test.ts +148 -0
  50. package/templates/base/tests/unit/server/services/audits.test.ts +219 -0
  51. package/templates/base/tests/unit/server/services/auth.test.ts +480 -3
  52. package/templates/base/tests/unit/server/services/invitations.test.ts +178 -0
  53. package/templates/base/tests/unit/server/services/users.test.ts +363 -8
  54. package/templates/base/tests/unit/shared/schemas.test.ts +1 -1
  55. package/templates/base/vite.config.ts +3 -1
  56. package/templates/base/.github/workflows/test.yml +0 -127
  57. package/templates/base/.husky/pre-push +0 -26
  58. package/templates/base/auth-setup-error.png +0 -0
  59. package/templates/base/pnpm-lock.yaml +0 -8052
  60. package/templates/base/tests/e2e/_auth/.gitkeep +0 -0
  61. package/templates/base/tsconfig.tsbuildinfo +0 -1
@@ -18,6 +18,17 @@ vi.mock('@server/db/sql', () => ({
18
18
  queryOne: vi.fn(),
19
19
  queryAll: vi.fn(),
20
20
  execute: vi.fn(),
21
+ toStringValue: (value: unknown) => {
22
+ if (typeof value === 'string') return value
23
+ if (typeof value === 'number' || typeof value === 'bigint') return String(value)
24
+ return ''
25
+ },
26
+ toNullableString: (value: unknown) => {
27
+ if (value === null || value === undefined) return null
28
+ if (typeof value === 'string') return value
29
+ if (typeof value === 'number' || typeof value === 'bigint') return String(value)
30
+ return null
31
+ },
21
32
  }))
22
33
 
23
34
  import { auditedUpdate, auditedDelete } from '@server/lib/audited-db'
@@ -82,7 +93,7 @@ describe('usersService', () => {
82
93
  ;(queryAll as Mock).mockResolvedValueOnce([
83
94
  {
84
95
  id: user.id,
85
- google_id: user.googleId,
96
+ google_id: `google-${user.id}`,
86
97
  email: user.email,
87
98
  name: user.name,
88
99
  avatar_url: user.avatarUrl,
@@ -115,7 +126,7 @@ describe('usersService', () => {
115
126
  ;(queryOne as Mock)
116
127
  .mockResolvedValueOnce({
117
128
  id: user.id,
118
- google_id: user.googleId,
129
+ google_id: `google-${user.id}`,
119
130
  email: user.email,
120
131
  name: user.name,
121
132
  avatar_url: user.avatarUrl,
@@ -138,7 +149,7 @@ describe('usersService', () => {
138
149
 
139
150
  ;(queryOne as Mock).mockResolvedValueOnce({
140
151
  id: user.id,
141
- google_id: user.googleId,
152
+ google_id: `google-${user.id}`,
142
153
  email: user.email,
143
154
  name: user.name,
144
155
  avatar_url: user.avatarUrl,
@@ -178,7 +189,7 @@ describe('usersService', () => {
178
189
  const user = createUserFixture({ id: 'user-1' })
179
190
  ;(queryOne as Mock).mockResolvedValueOnce({
180
191
  id: user.id,
181
- google_id: user.googleId,
192
+ google_id: `google-${user.id}`,
182
193
  email: user.email,
183
194
  name: user.name,
184
195
  avatar_url: user.avatarUrl,
@@ -201,7 +212,7 @@ describe('usersService', () => {
201
212
  const user = createUserFixture({ id: 'user-1', deletedAt: new Date().toISOString() })
202
213
  ;(queryOne as Mock).mockResolvedValueOnce({
203
214
  id: user.id,
204
- google_id: user.googleId,
215
+ google_id: `google-${user.id}`,
205
216
  email: user.email,
206
217
  name: user.name,
207
218
  avatar_url: user.avatarUrl,
@@ -243,7 +254,7 @@ describe('usersService', () => {
243
254
  ;(queryOne as Mock)
244
255
  .mockResolvedValueOnce({
245
256
  id: user.id,
246
- google_id: user.googleId,
257
+ google_id: `google-${user.id}`,
247
258
  email: user.email,
248
259
  name: user.name,
249
260
  avatar_url: user.avatarUrl,
@@ -274,7 +285,7 @@ describe('usersService', () => {
274
285
  ;(queryOne as Mock)
275
286
  .mockResolvedValueOnce({
276
287
  id: user.id,
277
- google_id: user.googleId,
288
+ google_id: `google-${user.id}`,
278
289
  email: user.email,
279
290
  name: user.name,
280
291
  avatar_url: user.avatarUrl,
@@ -300,7 +311,7 @@ describe('usersService', () => {
300
311
 
301
312
  ;(queryOne as Mock).mockResolvedValueOnce({
302
313
  id: user.id,
303
- google_id: user.googleId,
314
+ google_id: `google-${user.id}`,
304
315
  email: user.email,
305
316
  name: user.name,
306
317
  avatar_url: user.avatarUrl,
@@ -348,4 +359,348 @@ describe('usersService', () => {
348
359
  expect(logAudit).toHaveBeenCalled()
349
360
  })
350
361
  })
362
+
363
+ describe('findAll edge cases', () => {
364
+ const defaultPagination: PaginationQuery = { page: 1, limit: 10 }
365
+
366
+ it('should filter by query parameter', async () => {
367
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
368
+ ;(queryAll as Mock).mockResolvedValueOnce([
369
+ {
370
+ id: 'user-1',
371
+ google_id: 'google-1',
372
+ email: 'search@example.com',
373
+ name: 'Search User',
374
+ avatar_url: null,
375
+ status: 'active',
376
+ provider_ids: JSON.stringify(['google']),
377
+ is_super_admin: 0,
378
+ created_at: new Date().toISOString(),
379
+ updated_at: new Date().toISOString(),
380
+ deleted_at: null,
381
+ },
382
+ ])
383
+
384
+ const result = await usersService.findAll(db, superAdminCtx, { ...defaultPagination, query: 'search' })
385
+
386
+ expect(result.data).toHaveLength(1)
387
+ })
388
+
389
+ it('should filter by account for non-super-admin', async () => {
390
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
391
+ ;(queryAll as Mock).mockResolvedValueOnce([
392
+ {
393
+ id: 'user-1',
394
+ google_id: 'google-1',
395
+ email: 'user@example.com',
396
+ name: 'User',
397
+ avatar_url: null,
398
+ status: 'active',
399
+ provider_ids: '[]',
400
+ is_super_admin: 0,
401
+ created_at: new Date().toISOString(),
402
+ updated_at: new Date().toISOString(),
403
+ deleted_at: null,
404
+ },
405
+ ])
406
+
407
+ await usersService.findAll(db, ctx, defaultPagination)
408
+
409
+ // Check that account filter was applied
410
+ expect((queryOne as Mock).mock.calls[0][2]).toContain(ctx.accountId)
411
+ })
412
+
413
+ it('should handle null count result', async () => {
414
+ ;(queryOne as Mock).mockResolvedValueOnce(null)
415
+ ;(queryAll as Mock).mockResolvedValueOnce([])
416
+
417
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
418
+
419
+ expect(result.data).toHaveLength(0)
420
+ expect(result.meta.totalItems).toBe(0)
421
+ })
422
+
423
+ it('should parse providerIds as array', async () => {
424
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
425
+ ;(queryAll as Mock).mockResolvedValueOnce([
426
+ {
427
+ id: 'user-1',
428
+ google_id: 'google-1',
429
+ email: 'user@example.com',
430
+ name: 'User',
431
+ avatar_url: null,
432
+ status: 'active',
433
+ provider_ids: ['google', 'github'],
434
+ is_super_admin: 0,
435
+ created_at: new Date().toISOString(),
436
+ updated_at: new Date().toISOString(),
437
+ deleted_at: null,
438
+ },
439
+ ])
440
+
441
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
442
+
443
+ expect(result.data[0].providerIds).toEqual(['google', 'github'])
444
+ })
445
+
446
+ it('should handle invalid JSON in providerIds', async () => {
447
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
448
+ ;(queryAll as Mock).mockResolvedValueOnce([
449
+ {
450
+ id: 'user-1',
451
+ google_id: 'google-1',
452
+ email: 'user@example.com',
453
+ name: 'User',
454
+ avatar_url: null,
455
+ status: 'active',
456
+ provider_ids: 'invalid-json',
457
+ is_super_admin: 0,
458
+ created_at: new Date().toISOString(),
459
+ updated_at: new Date().toISOString(),
460
+ deleted_at: null,
461
+ },
462
+ ])
463
+
464
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
465
+
466
+ expect(result.data[0].providerIds).toEqual([])
467
+ })
468
+
469
+ it('should handle non-array JSON in providerIds', async () => {
470
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
471
+ ;(queryAll as Mock).mockResolvedValueOnce([
472
+ {
473
+ id: 'user-1',
474
+ google_id: 'google-1',
475
+ email: 'user@example.com',
476
+ name: 'User',
477
+ avatar_url: null,
478
+ status: 'active',
479
+ provider_ids: '{"key": "value"}',
480
+ is_super_admin: 0,
481
+ created_at: new Date().toISOString(),
482
+ updated_at: new Date().toISOString(),
483
+ deleted_at: null,
484
+ },
485
+ ])
486
+
487
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
488
+
489
+ expect(result.data[0].providerIds).toEqual([])
490
+ })
491
+
492
+ it('should handle isSuperAdmin as boolean true', async () => {
493
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
494
+ ;(queryAll as Mock).mockResolvedValueOnce([
495
+ {
496
+ id: 'user-1',
497
+ google_id: 'google-1',
498
+ email: 'user@example.com',
499
+ name: 'User',
500
+ avatar_url: null,
501
+ status: 'active',
502
+ provider_ids: '[]',
503
+ is_super_admin: true,
504
+ created_at: new Date().toISOString(),
505
+ updated_at: new Date().toISOString(),
506
+ deleted_at: null,
507
+ },
508
+ ])
509
+
510
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
511
+
512
+ expect(result.data[0].isSuperAdmin).toBe(true)
513
+ })
514
+
515
+ it('should handle isSuperAdmin as string "1"', async () => {
516
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
517
+ ;(queryAll as Mock).mockResolvedValueOnce([
518
+ {
519
+ id: 'user-1',
520
+ google_id: 'google-1',
521
+ email: 'user@example.com',
522
+ name: 'User',
523
+ avatar_url: null,
524
+ status: 'active',
525
+ provider_ids: '[]',
526
+ is_super_admin: '1',
527
+ created_at: new Date().toISOString(),
528
+ updated_at: new Date().toISOString(),
529
+ deleted_at: null,
530
+ },
531
+ ])
532
+
533
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
534
+
535
+ expect(result.data[0].isSuperAdmin).toBe(true)
536
+ })
537
+
538
+ it('should handle isSuperAdmin as string "true"', async () => {
539
+ ;(queryOne as Mock).mockResolvedValueOnce({ count: 1 })
540
+ ;(queryAll as Mock).mockResolvedValueOnce([
541
+ {
542
+ id: 'user-1',
543
+ google_id: 'google-1',
544
+ email: 'user@example.com',
545
+ name: 'User',
546
+ avatar_url: null,
547
+ status: 'active',
548
+ provider_ids: '[]',
549
+ is_super_admin: 'TRUE',
550
+ created_at: new Date().toISOString(),
551
+ updated_at: new Date().toISOString(),
552
+ deleted_at: null,
553
+ },
554
+ ])
555
+
556
+ const result = await usersService.findAll(db, superAdminCtx, defaultPagination)
557
+
558
+ expect(result.data[0].isSuperAdmin).toBe(true)
559
+ })
560
+ })
561
+
562
+ describe('update edge cases', () => {
563
+ it('should throw when auditedUpdate returns empty array', async () => {
564
+ const user = createUserFixture({ id: 'user-1' })
565
+
566
+ ;(queryOne as Mock).mockResolvedValueOnce({
567
+ id: user.id,
568
+ google_id: `google-${user.id}`,
569
+ email: user.email,
570
+ name: user.name,
571
+ avatar_url: user.avatarUrl,
572
+ status: user.status,
573
+ provider_ids: JSON.stringify(user.providerIds),
574
+ is_super_admin: user.isSuperAdmin ? 1 : 0,
575
+ created_at: user.createdAt,
576
+ updated_at: user.updatedAt,
577
+ deleted_at: user.deletedAt,
578
+ })
579
+
580
+ ;(auditedUpdate as Mock).mockResolvedValueOnce([])
581
+
582
+ await expect(usersService.update(db, superAdminCtx, user.id, { name: 'New' })).rejects.toThrow('Failed to update user')
583
+ })
584
+ })
585
+
586
+ describe('restore edge cases', () => {
587
+ it('should throw NotFoundError when user not found', async () => {
588
+ ;(queryOne as Mock).mockResolvedValueOnce(null)
589
+
590
+ await expect(usersService.restore(db, superAdminCtx, 'missing')).rejects.toThrow(NotFoundError)
591
+ })
592
+
593
+ it('should throw NotFoundError when restore fails', async () => {
594
+ const user = createUserFixture({ id: 'user-1', deletedAt: new Date().toISOString() })
595
+
596
+ ;(queryOne as Mock).mockResolvedValueOnce({
597
+ id: user.id,
598
+ google_id: `google-${user.id}`,
599
+ email: user.email,
600
+ name: user.name,
601
+ avatar_url: user.avatarUrl,
602
+ status: user.status,
603
+ provider_ids: JSON.stringify(user.providerIds),
604
+ is_super_admin: user.isSuperAdmin ? 1 : 0,
605
+ created_at: user.createdAt,
606
+ updated_at: user.updatedAt,
607
+ deleted_at: user.deletedAt,
608
+ })
609
+
610
+ ;(auditedUpdate as Mock).mockResolvedValueOnce([])
611
+
612
+ await expect(usersService.restore(db, superAdminCtx, user.id)).rejects.toThrow(NotFoundError)
613
+ })
614
+ })
615
+
616
+ describe('updateRole edge cases', () => {
617
+ it('should throw NotFoundError when membership not found', async () => {
618
+ const user = createUserFixture({ id: 'user-1' })
619
+
620
+ ;(queryOne as Mock)
621
+ .mockResolvedValueOnce({
622
+ id: user.id,
623
+ google_id: `google-${user.id}`,
624
+ email: user.email,
625
+ name: user.name,
626
+ avatar_url: user.avatarUrl,
627
+ status: user.status,
628
+ provider_ids: JSON.stringify(user.providerIds),
629
+ is_super_admin: user.isSuperAdmin ? 1 : 0,
630
+ created_at: user.createdAt,
631
+ updated_at: user.updatedAt,
632
+ deleted_at: user.deletedAt,
633
+ })
634
+ .mockResolvedValueOnce(null)
635
+
636
+ await expect(usersService.updateRole(db, superAdminCtx, user.id, 'account-1', 'ADMIN')).rejects.toThrow('User not found in account')
637
+ })
638
+ })
639
+
640
+ describe('createUserAccounts edge cases', () => {
641
+ it('should update existing membership', async () => {
642
+ const items = [{ userId: 'user-1', accountId: 'account-1', role: 'ADMIN' as const }]
643
+
644
+ ;(queryOne as Mock).mockResolvedValueOnce({ role: 'VIEWER' })
645
+
646
+ const result = await usersService.createUserAccounts(db, superAdminCtx, items)
647
+
648
+ expect(result.count).toBe(1)
649
+ expect(execute).toHaveBeenCalled()
650
+ })
651
+ })
652
+
653
+ describe('listUserRoles edge cases', () => {
654
+ it('should handle snake_case column names', async () => {
655
+ const user = createUserFixture({ id: 'user-1' })
656
+
657
+ ;(queryOne as Mock)
658
+ .mockResolvedValueOnce({
659
+ id: user.id,
660
+ google_id: `google-${user.id}`,
661
+ email: user.email,
662
+ name: user.name,
663
+ avatar_url: user.avatarUrl,
664
+ status: user.status,
665
+ provider_ids: JSON.stringify(user.providerIds),
666
+ is_super_admin: user.isSuperAdmin ? 1 : 0,
667
+ created_at: user.createdAt,
668
+ updated_at: user.updatedAt,
669
+ deleted_at: user.deletedAt,
670
+ })
671
+ .mockResolvedValueOnce({ ok: 1 })
672
+
673
+ ;(queryAll as Mock).mockResolvedValueOnce([{ account_id: 'account-1', role: 'ADMIN' }])
674
+
675
+ const result = await usersService.listUserRoles(db, ctx, user.id)
676
+
677
+ expect(result[0].accountId).toBe('account-1')
678
+ })
679
+ })
680
+
681
+ describe('findById edge cases', () => {
682
+ it('should return user for super admin without membership check', async () => {
683
+ const user = createUserFixture({ id: 'user-1' })
684
+
685
+ ;(queryOne as Mock).mockResolvedValueOnce({
686
+ id: user.id,
687
+ googleId: user.googleId,
688
+ email: user.email,
689
+ name: user.name,
690
+ avatarUrl: user.avatarUrl,
691
+ status: user.status,
692
+ providerIds: user.providerIds,
693
+ isSuperAdmin: user.isSuperAdmin,
694
+ createdAt: user.createdAt,
695
+ updatedAt: user.updatedAt,
696
+ deletedAt: user.deletedAt,
697
+ })
698
+
699
+ const result = await usersService.findById(db, superAdminCtx, user.id)
700
+
701
+ expect(result.id).toBe(user.id)
702
+ // queryOne should only be called once (no membership check)
703
+ expect(queryOne).toHaveBeenCalledTimes(1)
704
+ })
705
+ })
351
706
  })
@@ -7,7 +7,7 @@ import {
7
7
  UpdateAccountSchema,
8
8
  CreateInvitationSchema,
9
9
  CreateWebhookSchema,
10
- } from '../index'
10
+ } from '@shared/schemas'
11
11
 
12
12
  describe('User Schemas', () => {
13
13
  describe('CreateUserSchema', () => {
@@ -18,7 +18,9 @@ export default defineConfig({
18
18
  generatedRouteTree: "./src/client/routeTree.gen.ts",
19
19
  }),
20
20
  react(),
21
- cloudflare(),
21
+ cloudflare({
22
+ configPath: "./config/wrangler.json",
23
+ }),
22
24
  // Istanbul instrumentation for E2E coverage
23
25
  // Only add plugin when E2E_COVERAGE is true, then it instruments without additional env check
24
26
  ...(isE2ECoverage
@@ -1,127 +0,0 @@
1
- name: Test Suite
2
-
3
- on:
4
- push:
5
- branches: [main, master]
6
- pull_request:
7
- branches: [main, master]
8
-
9
- env:
10
- CI: true
11
- NODE_VERSION: '20'
12
-
13
- jobs:
14
- # Unit and Integration Tests (Vitest) with Coverage
15
- unit-tests:
16
- name: Unit Tests
17
- runs-on: ubuntu-latest
18
-
19
- steps:
20
- - name: Checkout code
21
- uses: actions/checkout@v4
22
-
23
- - name: Setup Node.js
24
- uses: actions/setup-node@v4
25
- with:
26
- node-version: ${{ env.NODE_VERSION }}
27
- cache: 'npm'
28
-
29
- - name: Install dependencies
30
- run: npm ci
31
-
32
- - name: Run backend tests with coverage
33
- run: npm run test:coverage
34
-
35
- - name: Run frontend tests with coverage
36
- run: npm run test:coverage:client
37
-
38
- - name: Upload coverage reports
39
- uses: codecov/codecov-action@v4
40
- if: always()
41
- with:
42
- files: ./coverage/server/lcov.info,./coverage/client/lcov.info
43
- flags: unittests
44
- fail_ci_if_error: false
45
-
46
- # E2E Tests (Playwright)
47
- e2e-tests:
48
- name: E2E Tests
49
- runs-on: ubuntu-latest
50
- timeout-minutes: 30
51
-
52
- steps:
53
- - name: Checkout code
54
- uses: actions/checkout@v4
55
-
56
- - name: Setup Node.js
57
- uses: actions/setup-node@v4
58
- with:
59
- node-version: ${{ env.NODE_VERSION }}
60
- cache: 'npm'
61
-
62
- - name: Install dependencies
63
- run: npm ci
64
-
65
- - name: Install Playwright browsers
66
- run: npx playwright install chromium --with-deps
67
-
68
- - name: Run E2E tests
69
- run: npm run test:e2e -- --project=chromium-unauth
70
-
71
- - name: Upload Playwright report
72
- uses: actions/upload-artifact@v4
73
- if: always()
74
- with:
75
- name: playwright-report
76
- path: playwright-report/
77
- retention-days: 7
78
-
79
- - name: Upload test results
80
- uses: actions/upload-artifact@v4
81
- if: failure()
82
- with:
83
- name: test-results
84
- path: test-results/
85
- retention-days: 7
86
-
87
- # Type checking
88
- typecheck:
89
- name: Type Check
90
- runs-on: ubuntu-latest
91
-
92
- steps:
93
- - name: Checkout code
94
- uses: actions/checkout@v4
95
-
96
- - name: Setup Node.js
97
- uses: actions/setup-node@v4
98
- with:
99
- node-version: ${{ env.NODE_VERSION }}
100
- cache: 'npm'
101
-
102
- - name: Install dependencies
103
- run: npm ci
104
-
105
- - name: Run type check
106
- run: npx tsc --noEmit
107
-
108
- # Linting
109
- lint:
110
- name: Lint
111
- runs-on: ubuntu-latest
112
-
113
- steps:
114
- - name: Checkout code
115
- uses: actions/checkout@v4
116
-
117
- - name: Setup Node.js
118
- uses: actions/setup-node@v4
119
- with:
120
- node-version: ${{ env.NODE_VERSION }}
121
- cache: 'npm'
122
-
123
- - name: Install dependencies
124
- run: npm ci
125
-
126
- - name: Run linter
127
- run: npm run lint
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env sh
2
-
3
- echo "๐Ÿ” Running pre-push checks..."
4
-
5
- # Type checking
6
- echo "๐Ÿ“ Type checking..."
7
- pnpm typecheck || {
8
- echo "โŒ Type check failed. Push aborted."
9
- exit 1
10
- }
11
-
12
- # Server unit tests only (faster, more reliable)
13
- echo "๐Ÿงช Running server unit tests..."
14
- pnpm test:unit:server || {
15
- echo "โŒ Server unit tests failed. Push aborted."
16
- exit 1
17
- }
18
-
19
- # Build verification
20
- echo "๐Ÿ—๏ธ Verifying build..."
21
- pnpm build || {
22
- echo "โŒ Build failed. Push aborted."
23
- exit 1
24
- }
25
-
26
- echo "โœ… All pre-push checks passed!"
Binary file