nextjs-hackathon-stack 0.1.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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.js +152 -0
- package/package.json +54 -0
- package/template/.cursor/agents/business-intelligence.md +38 -0
- package/template/.cursor/agents/code-reviewer.md +74 -0
- package/template/.cursor/agents/frontend.md +45 -0
- package/template/.cursor/agents/security-researcher.md +49 -0
- package/template/.cursor/agents/technical-lead.md +36 -0
- package/template/.cursor/agents/test-qa.md +85 -0
- package/template/.cursor/rules/ai.mdc +53 -0
- package/template/.cursor/rules/architecture.mdc +42 -0
- package/template/.cursor/rules/coding-standards.mdc +53 -0
- package/template/.cursor/rules/components.mdc +33 -0
- package/template/.cursor/rules/data-fetching.mdc +63 -0
- package/template/.cursor/rules/forms.mdc +59 -0
- package/template/.cursor/rules/general.mdc +52 -0
- package/template/.cursor/rules/nextjs.mdc +46 -0
- package/template/.cursor/rules/security.mdc +54 -0
- package/template/.cursor/rules/supabase.mdc +36 -0
- package/template/.cursor/rules/testing.mdc +116 -0
- package/template/.cursor/skills/create-api-route/SKILL.md +62 -0
- package/template/.cursor/skills/create-feature/SKILL.md +52 -0
- package/template/.cursor/skills/review-branch/SKILL.md +61 -0
- package/template/.cursor/skills/security-audit/SKILL.md +69 -0
- package/template/.env.example +24 -0
- package/template/.requirements/README.md +15 -0
- package/template/.requirements/auth.md +50 -0
- package/template/.requirements/template.md +25 -0
- package/template/README.md +98 -0
- package/template/components.json +19 -0
- package/template/drizzle.config.ts +10 -0
- package/template/eslint.config.ts +66 -0
- package/template/middleware.ts +10 -0
- package/template/next.config.ts +31 -0
- package/template/package.json.tmpl +62 -0
- package/template/playwright.config.ts +22 -0
- package/template/src/app/(auth)/login/page.tsx +12 -0
- package/template/src/app/(protected)/layout.tsx +19 -0
- package/template/src/app/(protected)/page.tsx +16 -0
- package/template/src/app/api/auth/callback/route.ts +21 -0
- package/template/src/app/globals.css +1 -0
- package/template/src/app/layout.tsx +21 -0
- package/template/src/e2e/chat.spec.ts +8 -0
- package/template/src/e2e/home.spec.ts +8 -0
- package/template/src/e2e/login.spec.ts +21 -0
- package/template/src/features/auth/__tests__/login-form.test.tsx +43 -0
- package/template/src/features/auth/__tests__/use-auth.test.ts +46 -0
- package/template/src/features/auth/actions/login.action.ts +38 -0
- package/template/src/features/auth/actions/logout.action.ts +10 -0
- package/template/src/features/auth/components/login-form.tsx +80 -0
- package/template/src/features/auth/hooks/use-auth.ts +17 -0
- package/template/src/features/chat/__tests__/chat-ui.test.tsx +30 -0
- package/template/src/features/chat/__tests__/route.test.ts +19 -0
- package/template/src/features/chat/api/route.ts +16 -0
- package/template/src/features/chat/components/chat-ui.tsx +47 -0
- package/template/src/features/chat/hooks/use-chat.ts +1 -0
- package/template/src/features/tts/__tests__/route.test.ts +13 -0
- package/template/src/features/tts/__tests__/tts-player.test.tsx +20 -0
- package/template/src/features/tts/api/route.ts +14 -0
- package/template/src/features/tts/components/tts-player.tsx +59 -0
- package/template/src/features/video/__tests__/route.test.ts +13 -0
- package/template/src/features/video/__tests__/video-generator.test.tsx +20 -0
- package/template/src/features/video/api/route.ts +14 -0
- package/template/src/features/video/components/video-generator.tsx +56 -0
- package/template/src/shared/__tests__/ai.test.ts +8 -0
- package/template/src/shared/__tests__/minimax-media.test.ts +57 -0
- package/template/src/shared/__tests__/providers.test.tsx +14 -0
- package/template/src/shared/__tests__/schema.test.ts +18 -0
- package/template/src/shared/__tests__/supabase-client.test.ts +13 -0
- package/template/src/shared/__tests__/supabase-server.test.ts +22 -0
- package/template/src/shared/components/providers.tsx +19 -0
- package/template/src/shared/db/index.ts +7 -0
- package/template/src/shared/db/schema.ts +15 -0
- package/template/src/shared/lib/ai.ts +8 -0
- package/template/src/shared/lib/minimax-media.ts +63 -0
- package/template/src/shared/lib/supabase/client.ts +8 -0
- package/template/src/shared/lib/supabase/middleware.ts +40 -0
- package/template/src/shared/lib/supabase/server.ts +26 -0
- package/template/src/test-setup.ts +1 -0
- package/template/tailwind.css +20 -0
- package/template/tsconfig.json +31 -0
- package/template/vitest.config.ts +33 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { generateVideo, synthesizeSpeech } from "../lib/minimax-media";
|
|
3
|
+
|
|
4
|
+
const mockFetch = vi.fn();
|
|
5
|
+
global.fetch = mockFetch;
|
|
6
|
+
|
|
7
|
+
describe("generateVideo", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns task_id when API responds successfully", async () => {
|
|
13
|
+
// Arrange
|
|
14
|
+
mockFetch.mockResolvedValue({
|
|
15
|
+
ok: true,
|
|
16
|
+
json: async () => ({ task_id: "abc123" }),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Act
|
|
20
|
+
const taskId = await generateVideo("A cat on the moon");
|
|
21
|
+
|
|
22
|
+
// Assert
|
|
23
|
+
expect(taskId).toBe("abc123");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("throws when API returns non-ok response", async () => {
|
|
27
|
+
// Arrange
|
|
28
|
+
mockFetch.mockResolvedValue({ ok: false, statusText: "Bad Request" });
|
|
29
|
+
|
|
30
|
+
// Act + Assert
|
|
31
|
+
await expect(generateVideo("test")).rejects.toThrow("Video generation failed");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("synthesizeSpeech", () => {
|
|
36
|
+
it("returns audio_file URL when API responds successfully", async () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
mockFetch.mockResolvedValue({
|
|
39
|
+
ok: true,
|
|
40
|
+
json: async () => ({ audio_file: "https://example.com/audio.mp3" }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
const url = await synthesizeSpeech("Hello world");
|
|
45
|
+
|
|
46
|
+
// Assert
|
|
47
|
+
expect(url).toBe("https://example.com/audio.mp3");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("throws when API returns non-ok response", async () => {
|
|
51
|
+
// Arrange
|
|
52
|
+
mockFetch.mockResolvedValue({ ok: false, statusText: "Server Error" });
|
|
53
|
+
|
|
54
|
+
// Act + Assert
|
|
55
|
+
await expect(synthesizeSpeech("test")).rejects.toThrow("TTS failed");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import { Providers } from "../components/providers";
|
|
4
|
+
|
|
5
|
+
describe("Providers", () => {
|
|
6
|
+
it("renders children", () => {
|
|
7
|
+
render(
|
|
8
|
+
<Providers>
|
|
9
|
+
<div data-testid="child">Hello</div>
|
|
10
|
+
</Providers>
|
|
11
|
+
);
|
|
12
|
+
expect(screen.getByTestId("child")).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { insertProfileSchema, selectProfileSchema } from "../db/schema";
|
|
3
|
+
|
|
4
|
+
describe("profiles schema", () => {
|
|
5
|
+
it("insertProfileSchema validates required fields", () => {
|
|
6
|
+
const result = insertProfileSchema.safeParse({ id: "uuid", email: "test@example.com" });
|
|
7
|
+
expect(result.success).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("insertProfileSchema rejects invalid email", () => {
|
|
11
|
+
const result = insertProfileSchema.safeParse({ id: "uuid", email: "not-email" });
|
|
12
|
+
expect(result.success).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("selectProfileSchema is defined", () => {
|
|
16
|
+
expect(selectProfileSchema).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@supabase/ssr", () => ({
|
|
4
|
+
createBrowserClient: vi.fn(() => ({ auth: {} })),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
describe("supabase client", () => {
|
|
8
|
+
it("createClient returns a supabase client", async () => {
|
|
9
|
+
const { createClient } = await import("../lib/supabase/client");
|
|
10
|
+
const client = createClient();
|
|
11
|
+
expect(client).toBeDefined();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@supabase/ssr", () => ({
|
|
4
|
+
createServerClient: vi.fn(() => ({ auth: {} })),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("next/headers", () => ({
|
|
8
|
+
cookies: vi.fn(() =>
|
|
9
|
+
Promise.resolve({
|
|
10
|
+
getAll: () => [],
|
|
11
|
+
set: vi.fn(),
|
|
12
|
+
})
|
|
13
|
+
),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe("supabase server", () => {
|
|
17
|
+
it("createClient returns a supabase client", async () => {
|
|
18
|
+
const { createClient } = await import("../lib/supabase/server");
|
|
19
|
+
const client = await createClient();
|
|
20
|
+
expect(client).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
7
|
+
const [queryClient] = useState(
|
|
8
|
+
() =>
|
|
9
|
+
new QueryClient({
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
queries: {
|
|
12
|
+
staleTime: 60 * 1000,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
2
|
+
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
|
3
|
+
|
|
4
|
+
export const profiles = pgTable("profiles", {
|
|
5
|
+
id: uuid("id").primaryKey(),
|
|
6
|
+
email: text("email").notNull().unique(),
|
|
7
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
8
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const insertProfileSchema = createInsertSchema(profiles);
|
|
12
|
+
export const selectProfileSchema = createSelectSchema(profiles);
|
|
13
|
+
|
|
14
|
+
export type InsertProfile = typeof profiles.$inferInsert;
|
|
15
|
+
export type SelectProfile = typeof profiles.$inferSelect;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
|
+
|
|
3
|
+
const gateway = createOpenAI({
|
|
4
|
+
baseURL: process.env["AI_GATEWAY_URL"] ?? "https://api.minimaxi.chat/v1",
|
|
5
|
+
apiKey: process.env["MINIMAX_API_KEY"] ?? "",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const aiModel = gateway("minimax/minimax-m2.7");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const MINIMAX_API_BASE = "https://api.minimaxi.chat/v1";
|
|
2
|
+
|
|
3
|
+
interface VideoGenerationRequest {
|
|
4
|
+
model: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface VideoGenerationResponse {
|
|
9
|
+
task_id: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TtsRequest {
|
|
13
|
+
model: string;
|
|
14
|
+
text: string;
|
|
15
|
+
voice_id: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TtsResponse {
|
|
19
|
+
audio_file: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function generateVideo(prompt: string): Promise<string> {
|
|
23
|
+
const response = await fetch(`${MINIMAX_API_BASE}/video_generation`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${process.env["MINIMAX_API_KEY"] ?? ""}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
model: "video-01",
|
|
31
|
+
prompt,
|
|
32
|
+
} satisfies VideoGenerationRequest),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Video generation failed: ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const data = (await response.json()) as VideoGenerationResponse;
|
|
40
|
+
return data.task_id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function synthesizeSpeech(text: string, voiceId = "female-shaonv"): Promise<string> {
|
|
44
|
+
const response = await fetch(`${MINIMAX_API_BASE}/t2a_v2`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${process.env["MINIMAX_API_KEY"] ?? ""}`,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
model: "speech-02-hd",
|
|
52
|
+
text,
|
|
53
|
+
voice_id: voiceId,
|
|
54
|
+
} satisfies TtsRequest),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`TTS failed: ${response.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = (await response.json()) as TtsResponse;
|
|
62
|
+
return data.audio_file;
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createServerClient } from "@supabase/ssr";
|
|
2
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
3
|
+
|
|
4
|
+
export async function updateSession(request: NextRequest) {
|
|
5
|
+
let supabaseResponse = NextResponse.next({ request });
|
|
6
|
+
|
|
7
|
+
const supabase = createServerClient(
|
|
8
|
+
process.env["NEXT_PUBLIC_SUPABASE_URL"]!,
|
|
9
|
+
process.env["NEXT_PUBLIC_SUPABASE_ANON_KEY"]!,
|
|
10
|
+
{
|
|
11
|
+
cookies: {
|
|
12
|
+
getAll() {
|
|
13
|
+
return request.cookies.getAll();
|
|
14
|
+
},
|
|
15
|
+
setAll(cookiesToSet) {
|
|
16
|
+
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
|
|
17
|
+
supabaseResponse = NextResponse.next({ request });
|
|
18
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
19
|
+
supabaseResponse.cookies.set(name, value, options)
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
data: { user },
|
|
28
|
+
} = await supabase.auth.getUser();
|
|
29
|
+
|
|
30
|
+
const isAuthRoute = request.nextUrl.pathname.startsWith("/login");
|
|
31
|
+
const isProtectedRoute = !isAuthRoute && !request.nextUrl.pathname.startsWith("/api/auth");
|
|
32
|
+
|
|
33
|
+
if (!user && isProtectedRoute && request.nextUrl.pathname !== "/") {
|
|
34
|
+
const redirectUrl = request.nextUrl.clone();
|
|
35
|
+
redirectUrl.pathname = "/login";
|
|
36
|
+
return NextResponse.redirect(redirectUrl);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return supabaseResponse;
|
|
40
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createServerClient } from "@supabase/ssr";
|
|
2
|
+
import { cookies } from "next/headers";
|
|
3
|
+
|
|
4
|
+
export async function createClient() {
|
|
5
|
+
const cookieStore = await cookies();
|
|
6
|
+
|
|
7
|
+
return createServerClient(
|
|
8
|
+
process.env["NEXT_PUBLIC_SUPABASE_URL"]!,
|
|
9
|
+
process.env["NEXT_PUBLIC_SUPABASE_ANON_KEY"]!,
|
|
10
|
+
{
|
|
11
|
+
cookies: {
|
|
12
|
+
getAll() {
|
|
13
|
+
return cookieStore.getAll();
|
|
14
|
+
},
|
|
15
|
+
setAll(cookiesToSet) {
|
|
16
|
+
try {
|
|
17
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
18
|
+
cookieStore.set(name, value, options)
|
|
19
|
+
);
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-background: hsl(0 0% 100%);
|
|
5
|
+
--color-foreground: hsl(222.2 84% 4.9%);
|
|
6
|
+
--color-primary: hsl(222.2 47.4% 11.2%);
|
|
7
|
+
--color-primary-foreground: hsl(210 40% 98%);
|
|
8
|
+
--color-secondary: hsl(210 40% 96.1%);
|
|
9
|
+
--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
|
|
10
|
+
--color-muted: hsl(210 40% 96.1%);
|
|
11
|
+
--color-muted-foreground: hsl(215.4 16.3% 46.9%);
|
|
12
|
+
--color-accent: hsl(210 40% 96.1%);
|
|
13
|
+
--color-accent-foreground: hsl(222.2 47.4% 11.2%);
|
|
14
|
+
--color-destructive: hsl(0 84.2% 60.2%);
|
|
15
|
+
--color-destructive-foreground: hsl(210 40% 98%);
|
|
16
|
+
--color-border: hsl(214.3 31.8% 91.4%);
|
|
17
|
+
--color-input: hsl(214.3 31.8% 91.4%);
|
|
18
|
+
--color-ring: hsl(222.2 84% 4.9%);
|
|
19
|
+
--radius: 0.5rem;
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"exactOptionalPropertyTypes": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noUnusedLocals": true,
|
|
14
|
+
"noUnusedParameters": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"module": "esnext",
|
|
19
|
+
"moduleResolution": "bundler",
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"jsx": "preserve",
|
|
23
|
+
"incremental": true,
|
|
24
|
+
"plugins": [{ "name": "next" }],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": ["./src/*"]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
30
|
+
"exclude": ["node_modules"]
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
test: {
|
|
8
|
+
environment: "jsdom",
|
|
9
|
+
setupFiles: ["./src/test-setup.ts"],
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: "v8",
|
|
12
|
+
thresholds: {
|
|
13
|
+
branches: 100,
|
|
14
|
+
functions: 100,
|
|
15
|
+
lines: 100,
|
|
16
|
+
statements: 100,
|
|
17
|
+
},
|
|
18
|
+
exclude: [
|
|
19
|
+
"node_modules/**",
|
|
20
|
+
"src/e2e/**",
|
|
21
|
+
"src/shared/db/migrations/**",
|
|
22
|
+
"**/*.config.*",
|
|
23
|
+
"**/index.ts",
|
|
24
|
+
"src/app/globals.css",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
resolve: {
|
|
29
|
+
alias: {
|
|
30
|
+
"@": resolve(__dirname, "./src"),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|