create-n8-app 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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +239 -0
  5. package/package.json +58 -0
  6. package/template/_env.example +34 -0
  7. package/template/_env.local +31 -0
  8. package/template/_eslintrc.json +3 -0
  9. package/template/_gitignore +46 -0
  10. package/template/_package.json +59 -0
  11. package/template/app/api/auth/[...nextauth]/route.ts +3 -0
  12. package/template/app/api/trpc/[trpc]/route.ts +19 -0
  13. package/template/app/auth/signin/page.tsx +39 -0
  14. package/template/app/globals.css +33 -0
  15. package/template/app/layout.tsx +28 -0
  16. package/template/app/page.tsx +68 -0
  17. package/template/app/providers.tsx +47 -0
  18. package/template/components/auth/user-button.tsx +46 -0
  19. package/template/components/ui/.gitkeep +2 -0
  20. package/template/components.json +21 -0
  21. package/template/drizzle/.gitkeep +1 -0
  22. package/template/drizzle.config.ts +10 -0
  23. package/template/hooks/.gitkeep +1 -0
  24. package/template/lib/auth.ts +44 -0
  25. package/template/lib/db.ts +10 -0
  26. package/template/lib/env.ts +76 -0
  27. package/template/lib/trpc.ts +4 -0
  28. package/template/lib/utils.ts +6 -0
  29. package/template/next-env.d.ts +5 -0
  30. package/template/next.config.ts +14 -0
  31. package/template/postcss.config.mjs +7 -0
  32. package/template/prettier.config.js +11 -0
  33. package/template/public/.gitkeep +1 -0
  34. package/template/server/api/root.ts +22 -0
  35. package/template/server/api/routers/example.ts +76 -0
  36. package/template/server/api/trpc.ts +67 -0
  37. package/template/server/db/schema.ts +95 -0
  38. package/template/stores/example-store.ts +121 -0
  39. package/template/tests/example.test.ts +22 -0
  40. package/template/tests/setup.ts +22 -0
  41. package/template/tsconfig.json +27 -0
  42. package/template/vitest.config.ts +30 -0
@@ -0,0 +1,95 @@
1
+ import {
2
+ pgTable,
3
+ text,
4
+ timestamp,
5
+ varchar,
6
+ serial,
7
+ integer,
8
+ primaryKey,
9
+ } from 'drizzle-orm/pg-core'
10
+ import type { AdapterAccountType } from 'next-auth/adapters'
11
+
12
+ // ============================================================================
13
+ // Auth Tables (NextAuth.js / Auth.js)
14
+ // ============================================================================
15
+
16
+ export const users = pgTable('users', {
17
+ id: text('id')
18
+ .primaryKey()
19
+ .$defaultFn(() => crypto.randomUUID()),
20
+ name: text('name'),
21
+ email: text('email').unique(),
22
+ emailVerified: timestamp('email_verified', { mode: 'date' }),
23
+ image: text('image'),
24
+ createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
25
+ updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull(),
26
+ })
27
+
28
+ export const accounts = pgTable(
29
+ 'accounts',
30
+ {
31
+ userId: text('user_id')
32
+ .notNull()
33
+ .references(() => users.id, { onDelete: 'cascade' }),
34
+ type: text('type').$type<AdapterAccountType>().notNull(),
35
+ provider: text('provider').notNull(),
36
+ providerAccountId: text('provider_account_id').notNull(),
37
+ refresh_token: text('refresh_token'),
38
+ access_token: text('access_token'),
39
+ expires_at: integer('expires_at'),
40
+ token_type: text('token_type'),
41
+ scope: text('scope'),
42
+ id_token: text('id_token'),
43
+ session_state: text('session_state'),
44
+ },
45
+ (account) => [
46
+ primaryKey({
47
+ columns: [account.provider, account.providerAccountId],
48
+ }),
49
+ ]
50
+ )
51
+
52
+ export const sessions = pgTable('sessions', {
53
+ sessionToken: text('session_token').primaryKey(),
54
+ userId: text('user_id')
55
+ .notNull()
56
+ .references(() => users.id, { onDelete: 'cascade' }),
57
+ expires: timestamp('expires', { mode: 'date' }).notNull(),
58
+ })
59
+
60
+ export const verificationTokens = pgTable(
61
+ 'verification_tokens',
62
+ {
63
+ identifier: text('identifier').notNull(),
64
+ token: text('token').notNull(),
65
+ expires: timestamp('expires', { mode: 'date' }).notNull(),
66
+ },
67
+ (verificationToken) => [
68
+ primaryKey({
69
+ columns: [verificationToken.identifier, verificationToken.token],
70
+ }),
71
+ ]
72
+ )
73
+
74
+ // ============================================================================
75
+ // Example Application Tables
76
+ // ============================================================================
77
+
78
+ export const posts = pgTable('posts', {
79
+ id: serial('id').primaryKey(),
80
+ title: varchar('title', { length: 256 }).notNull(),
81
+ content: text('content'),
82
+ authorId: text('author_id').references(() => users.id, { onDelete: 'set null' }),
83
+ createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(),
84
+ updatedAt: timestamp('updated_at', { mode: 'date' }).defaultNow().notNull(),
85
+ })
86
+
87
+ // ============================================================================
88
+ // Type Exports
89
+ // ============================================================================
90
+
91
+ export type User = typeof users.$inferSelect
92
+ export type NewUser = typeof users.$inferInsert
93
+
94
+ export type Post = typeof posts.$inferSelect
95
+ export type NewPost = typeof posts.$inferInsert
@@ -0,0 +1,121 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+
4
+ /**
5
+ * Example Zustand store for managing global UI state
6
+ *
7
+ * Use Zustand for:
8
+ * - Shopping cart state
9
+ * - UI state (modals, sidebars, theme)
10
+ * - User preferences and settings
11
+ * - Global app state shared across components
12
+ *
13
+ * DON'T use Zustand for:
14
+ * - Server data (use React Query instead)
15
+ * - Form state (use React Hook Form or local state)
16
+ * - Local component state (use useState)
17
+ */
18
+
19
+ interface UIState {
20
+ // Sidebar state
21
+ sidebarOpen: boolean
22
+ toggleSidebar: () => void
23
+ setSidebarOpen: (open: boolean) => void
24
+
25
+ // Theme state
26
+ theme: 'light' | 'dark' | 'system'
27
+ setTheme: (theme: 'light' | 'dark' | 'system') => void
28
+
29
+ // Modal state
30
+ activeModal: string | null
31
+ openModal: (modalId: string) => void
32
+ closeModal: () => void
33
+ }
34
+
35
+ export const useUIStore = create<UIState>()(
36
+ persist(
37
+ (set) => ({
38
+ // Sidebar
39
+ sidebarOpen: true,
40
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
41
+ setSidebarOpen: (open) => set({ sidebarOpen: open }),
42
+
43
+ // Theme
44
+ theme: 'system',
45
+ setTheme: (theme) => set({ theme }),
46
+
47
+ // Modal
48
+ activeModal: null,
49
+ openModal: (modalId) => set({ activeModal: modalId }),
50
+ closeModal: () => set({ activeModal: null }),
51
+ }),
52
+ {
53
+ name: 'ui-storage', // localStorage key
54
+ partialize: (state) => ({
55
+ // Only persist these fields
56
+ sidebarOpen: state.sidebarOpen,
57
+ theme: state.theme,
58
+ }),
59
+ }
60
+ )
61
+ )
62
+
63
+ /**
64
+ * Example cart store (common use case)
65
+ */
66
+ interface CartItem {
67
+ id: string
68
+ name: string
69
+ price: number
70
+ quantity: number
71
+ }
72
+
73
+ interface CartState {
74
+ items: CartItem[]
75
+ addItem: (item: Omit<CartItem, 'quantity'>) => void
76
+ removeItem: (id: string) => void
77
+ updateQuantity: (id: string, quantity: number) => void
78
+ clearCart: () => void
79
+ totalItems: () => number
80
+ totalPrice: () => number
81
+ }
82
+
83
+ export const useCartStore = create<CartState>()(
84
+ persist(
85
+ (set, get) => ({
86
+ items: [],
87
+
88
+ addItem: (item) =>
89
+ set((state) => {
90
+ const existingItem = state.items.find((i) => i.id === item.id)
91
+ if (existingItem) {
92
+ return {
93
+ items: state.items.map((i) =>
94
+ i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
95
+ ),
96
+ }
97
+ }
98
+ return { items: [...state.items, { ...item, quantity: 1 }] }
99
+ }),
100
+
101
+ removeItem: (id) =>
102
+ set((state) => ({
103
+ items: state.items.filter((i) => i.id !== id),
104
+ })),
105
+
106
+ updateQuantity: (id, quantity) =>
107
+ set((state) => ({
108
+ items: state.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
109
+ })),
110
+
111
+ clearCart: () => set({ items: [] }),
112
+
113
+ totalItems: () => get().items.reduce((sum, item) => sum + item.quantity, 0),
114
+
115
+ totalPrice: () => get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
116
+ }),
117
+ {
118
+ name: 'cart-storage',
119
+ }
120
+ )
121
+ )
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ describe('Example tests', () => {
4
+ it('should add numbers correctly', () => {
5
+ expect(1 + 1).toBe(2)
6
+ })
7
+
8
+ it('should handle string concatenation', () => {
9
+ expect('Hello' + ' ' + 'World').toBe('Hello World')
10
+ })
11
+
12
+ it('should handle arrays', () => {
13
+ const arr = [1, 2, 3]
14
+ expect(arr).toHaveLength(3)
15
+ expect(arr).toContain(2)
16
+ })
17
+
18
+ it('should handle async operations', async () => {
19
+ const result = await Promise.resolve('success')
20
+ expect(result).toBe('success')
21
+ })
22
+ })
@@ -0,0 +1,22 @@
1
+ import '@testing-library/jest-dom/vitest'
2
+ import { vi } from 'vitest'
3
+
4
+ // Mock Next.js router
5
+ vi.mock('next/navigation', () => ({
6
+ useRouter: () => ({
7
+ push: vi.fn(),
8
+ replace: vi.fn(),
9
+ prefetch: vi.fn(),
10
+ back: vi.fn(),
11
+ }),
12
+ usePathname: () => '/',
13
+ useSearchParams: () => new URLSearchParams(),
14
+ }))
15
+
16
+ // Mock next-auth
17
+ vi.mock('next-auth/react', () => ({
18
+ useSession: () => ({ data: null, status: 'unauthenticated' }),
19
+ signIn: vi.fn(),
20
+ signOut: vi.fn(),
21
+ SessionProvider: ({ children }: { children: React.ReactNode }) => children,
22
+ }))
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import react from '@vitejs/plugin-react'
3
+ import path from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ test: {
8
+ environment: 'jsdom',
9
+ globals: true,
10
+ setupFiles: ['./tests/setup.ts'],
11
+ include: ['**/*.{test,spec}.{ts,tsx}'],
12
+ exclude: ['node_modules', '.next', 'dist'],
13
+ coverage: {
14
+ provider: 'v8',
15
+ reporter: ['text', 'json', 'html'],
16
+ exclude: [
17
+ 'node_modules/',
18
+ '.next/',
19
+ '**/*.d.ts',
20
+ 'tests/setup.ts',
21
+ 'vitest.config.ts',
22
+ ],
23
+ },
24
+ },
25
+ resolve: {
26
+ alias: {
27
+ '@': path.resolve(__dirname, './'),
28
+ },
29
+ },
30
+ })