@igniter-js/cli 0.1.3 → 0.1.5

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 (38) hide show
  1. package/dist/index.js +353 -191
  2. package/dist/index.js.map +1 -0
  3. package/dist/templates/copilot.form.instructions.hbs +1021 -0
  4. package/dist/templates/express.server.hbs +33 -0
  5. package/dist/templates/feature.controller.hbs +14 -4
  6. package/dist/templates/feature.interface.hbs +29 -9
  7. package/dist/templates/feature.procedure.hbs +27 -16
  8. package/dist/templates/globals.hbs +7 -82
  9. package/dist/templates/igniter.context.hbs +1 -1
  10. package/dist/templates/layout.hbs +6 -14
  11. package/dist/templates/page.hbs +4 -37
  12. package/dist/templates/readme.hbs +25 -42
  13. package/dist/templates/use-form-with-zod.hbs +39 -0
  14. package/dist/templates/vscode.settings.hbs +3 -0
  15. package/dist/utils/analyze.js +8 -10
  16. package/dist/utils/analyze.js.map +1 -0
  17. package/dist/utils/cli-style.d.ts +55 -0
  18. package/dist/utils/cli-style.js +171 -0
  19. package/dist/utils/cli-style.js.map +1 -0
  20. package/dist/utils/consts.d.ts +4 -9
  21. package/dist/utils/consts.js +6 -13
  22. package/dist/utils/consts.js.map +1 -0
  23. package/dist/utils/handlebars-helpers.js +31 -6
  24. package/dist/utils/handlebars-helpers.js.map +1 -0
  25. package/dist/utils/helpers.d.ts +1 -1
  26. package/dist/utils/helpers.js +4 -5
  27. package/dist/utils/helpers.js.map +1 -0
  28. package/dist/utils/platform-utils.d.ts +40 -0
  29. package/dist/utils/platform-utils.js +76 -0
  30. package/dist/utils/platform-utils.js.map +1 -0
  31. package/dist/utils/prisma-schema-parser.js +11 -16
  32. package/dist/utils/prisma-schema-parser.js.map +1 -0
  33. package/dist/utils/project-utils.d.ts +32 -0
  34. package/dist/utils/project-utils.js +123 -0
  35. package/dist/utils/project-utils.js.map +1 -0
  36. package/dist/utils/template-handler.js +4 -5
  37. package/dist/utils/template-handler.js.map +1 -0
  38. package/package.json +3 -1
@@ -0,0 +1,33 @@
1
+ import express, { Request, Response, NextFunction } from 'express';
2
+ import cors from 'cors';
3
+ import { AppRouter } from './igniter.router';
4
+ import { createIgniterAppContext } from './igniter.context';
5
+
6
+ // Initialize Express server
7
+ const app = express();
8
+
9
+ // Configure middleware
10
+ app.use(cors());
11
+ app.use(express.json());
12
+ app.use(express.urlencoded({ extended: true }));
13
+
14
+ // Mount Igniter router
15
+ app.use(async (req: Request, res: Response) => {
16
+ return AppRouter.handler(req)
17
+ });
18
+
19
+ // Global error handler
20
+ app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
21
+ console.error('Express error handler:', err);
22
+
23
+ res.status(500).json({
24
+ status: 500,
25
+ message: 'Internal Server Error'
26
+ });
27
+ });
28
+
29
+ // Start server
30
+ app.listen(PORT, () => {
31
+ console.log(`✅ Server started at http://localhost:${PORT}`);
32
+ console.log(`🚀 API routes available at http://localhost:${PORT}/api/v1`);
33
+ });
@@ -1,4 +1,6 @@
1
+ {{#unless noModel}}
1
2
  import { z } from "zod";
3
+ {{/unless}}
2
4
  import { igniter } from "@/igniter";
3
5
  import { {{pascalCase name}}FeatureProcedure } from "../procedures/{{kebabCase name}}.procedure";
4
6
 
@@ -6,6 +8,17 @@ export const {{pascalCase name}}Controller = igniter.controller({
6
8
  name: "{{kebabCase name}}",
7
9
  path: "/{{kebabCase name}}",
8
10
  actions: {
11
+ {{#if noModel}}
12
+ hello: igniter.query({
13
+ method: "GET",
14
+ path: "/hello",
15
+ use: [{{pascalCase name}}FeatureProcedure()],
16
+ handler: async ({ response, context }) => {
17
+ const result = await context.{{camelCase name}}.hello();
18
+ return response.success(result);
19
+ }
20
+ }),
21
+ {{else}}
9
22
  findMany: igniter.query({
10
23
  method: "GET",
11
24
  path: "/",
@@ -22,7 +35,6 @@ export const {{pascalCase name}}Controller = igniter.controller({
22
35
  return response.success(result);
23
36
  }
24
37
  }),
25
-
26
38
  findOne: igniter.query({
27
39
  method: "GET",
28
40
  path: "/:id" as const,
@@ -32,7 +44,6 @@ export const {{pascalCase name}}Controller = igniter.controller({
32
44
  return response.success(result);
33
45
  }
34
46
  }),
35
-
36
47
  create: igniter.mutation({
37
48
  method: "POST",
38
49
  path: "/",
@@ -49,7 +60,6 @@ export const {{pascalCase name}}Controller = igniter.controller({
49
60
  return response.success(result);
50
61
  }
51
62
  }),
52
-
53
63
  update: igniter.mutation({
54
64
  method: "PUT",
55
65
  path: "/:id" as const,
@@ -71,7 +81,6 @@ export const {{pascalCase name}}Controller = igniter.controller({
71
81
  return response.success(result);
72
82
  }
73
83
  }),
74
-
75
84
  delete: igniter.mutation({
76
85
  method: "DELETE",
77
86
  path: "/:id" as const,
@@ -81,5 +90,6 @@ export const {{pascalCase name}}Controller = igniter.controller({
81
90
  return response.success(null);
82
91
  }
83
92
  })
93
+ {{/if}}
84
94
  }
85
95
  });
@@ -1,8 +1,29 @@
1
+ {{#if noModel}}
2
+ /**
3
+ * Represents a basic interface for the {{pascalCase name}} feature.
4
+ * Since this feature was created without a Prisma model, you can extend
5
+ * this interface with your own properties as needed.
6
+ */
7
+ export interface {{pascalCase name}}Response {
8
+ message: string;
9
+ }
10
+ {{else}}
1
11
  {{#each fields}}
2
12
  {{#if relations}}
3
13
  import type { {{pascalCase type}} } from '../{{kebabCase type}}/{{kebabCase type}}.interface';
4
14
  {{/if}}
5
15
  {{/each}}
16
+ {{! Seção para gerar os Enums }}
17
+ {{#each enums}}
18
+ /**
19
+ * Enum representing the possible values for {{name}}
20
+ */
21
+ export enum {{pascalCase name}} {
22
+ {{#each values}}
23
+ {{this}} = '{{this}}',
24
+ {{/each}}
25
+ }
26
+ {{/each}}
6
27
 
7
28
  /**
8
29
  * Represents a {{pascalCase name}} entity.
@@ -11,17 +32,15 @@ export interface {{pascalCase name}} {
11
32
  {{#each fields}}
12
33
  {{#if isRelation}}
13
34
  {{#if isList}}
14
- /** Array of IDs representing the related {{pascalCase type}} entities */
15
- {{name}}Ids: string[];
16
35
  /** Related {{pascalCase type}} entities */
17
- {{name}}: {{pascalCase type}}[];
36
+ {{name}}?: {{pascalCase type}}[];
18
37
  {{else}}
19
38
  /** Related {{pascalCase type}} entity */
20
- {{name}}: {{pascalCase type}};
39
+ {{name}}{{#if isOptional}}?{{/if}}: {{pascalCase type}};
21
40
  {{/if}}
22
41
  {{else}}
23
42
  /** {{pascalCase name}}'s {{name}} property */
24
- {{name}}: {{getTypeFormat type isRelation}}{{#if isOptional}}{{#unless hasDefault}} | null{{/unless}}{{/if}};
43
+ {{name}}: {{getTypeFormat type isRelation isEnum}}{{#if isOptional}}{{#unless hasDefault}} | null{{/unless}}{{/if}};
25
44
  {{/if}}
26
45
  {{/each}}
27
46
  }
@@ -38,7 +57,7 @@ export interface Create{{pascalCase name}}DTO {
38
57
  {{#unless (equals name (concat (lowerCase ../name) "Id"))}}
39
58
  {{#unless isRelation}}
40
59
  /** {{pascalCase name}}'s {{name}} property */
41
- {{name}}{{#if isOptional}}?{{/if}}: {{getTypeFormat type isRelation}}{{#if isOptional}} | null{{/if}};
60
+ {{name}}: {{getTypeFormat type isRelation isEnum}}{{#if isOptional}}{{#unless hasDefault}} | null{{/unless}}{{/if}};
42
61
  {{/unless}}
43
62
  {{/unless}}
44
63
  {{/if}}
@@ -57,7 +76,7 @@ export interface Update{{pascalCase name}}DTO {
57
76
  {{#unless (equals name (concat (lowerCase ../name) "Id"))}}
58
77
  {{#unless isRelation}}
59
78
  /** {{pascalCase name}}'s {{name}} property */
60
- {{name}}?: {{getTypeFormat type isRelation}}{{#if isOptional}} | null{{/if}};
79
+ {{name}}?: {{getTypeFormat type isRelation isEnum}}{{#if isOptional}}{{#unless hasDefault}} | null{{/unless}}{{/if}};
61
80
  {{/unless}}
62
81
  {{/unless}}
63
82
  {{/if}}
@@ -65,7 +84,7 @@ export interface Update{{pascalCase name}}DTO {
65
84
  }
66
85
 
67
86
  /**
68
- * Query parameters for fetching Category entities
87
+ * Query parameters for fetching {{pascalCase name}} entities
69
88
  */
70
89
  export interface {{pascalCase name}}QueryParams {
71
90
  /** Current page number for pagination */
@@ -78,4 +97,5 @@ export interface {{pascalCase name}}QueryParams {
78
97
  sortOrder?: 'asc' | 'desc';
79
98
  /** Search term for filtering */
80
99
  search?: string;
81
- }
100
+ }
101
+ {{/if}}
@@ -1,76 +1,87 @@
1
1
  import { igniter } from "@/igniter";
2
+ {{#unless noModel}}
2
3
  import type { {{pascalCase name}}, Create{{pascalCase name}}DTO, Update{{pascalCase name}}DTO, {{pascalCase name}}QueryParams } from "../{{kebabCase name}}.interface";
4
+ {{/unless}}
3
5
 
4
6
  export const {{pascalCase name}}FeatureProcedure = igniter.procedure({
5
7
  name: "{{pascalCase name}}FeatureProcedure",
8
+ {{#if noModel}}
9
+ handler: async () => {
10
+ {{else}}
6
11
  handler: async (_, { context }) => {
12
+ {{/if}}
7
13
  return {
8
14
  {{camelCase name}}: {
15
+ {{#if noModel}}
16
+ hello: async (): Promise<{ message: string }> => {
17
+ return { message: 'Hello from {{pascalCase name}} feature!' };
18
+ }
19
+ {{else}}
9
20
  findMany: async (query: {{pascalCase name}}QueryParams): Promise<{{pascalCase name}}[]> => {
10
- return context.providers.database.{{camelCase name}}.findMany({
21
+ return context.providers.database.{{lowerCase name }}.findMany({
11
22
  where: query.search ? {
12
23
  OR: [
13
24
  {{#each fields}}
14
25
  {{#unless isRelation}}
26
+ {{#unless (or (equals name 'id') (equals name 'createdAt') (equals name 'updatedAt'))}}
27
+ {{#if (equals type 'String')}}
15
28
  { {{name}}: { contains: query.search } },
29
+ {{/if}}
30
+ {{/unless}}
16
31
  {{/unless}}
17
32
  {{/each}}
18
33
  ]
19
34
  } : undefined,
20
35
  skip: query.page ? (query.page - 1) * (query.limit || 10) : undefined,
21
36
  take: query.limit,
22
- orderBy: query.sortBy ? {
23
- [query.sortBy]: query.sortOrder || 'asc'
24
- } : undefined
37
+ orderBy: query.sortBy ? {[query.sortBy]: query.sortOrder || 'asc'} : undefined
25
38
  });
26
39
  },
27
-
28
40
  findOne: async (params: { id: string }): Promise<{{pascalCase name}} | null> => {
29
- return context.providers.database.{{camelCase name}}.findUnique({
41
+ return context.providers.database.{{camelCase name }}.findUnique({
30
42
  where: {
31
43
  id: params.id
32
44
  }
33
45
  });
34
46
  },
35
-
36
47
  create: async (input: Create{{pascalCase name}}DTO): Promise<{{pascalCase name}}> => {
37
- return context.providers.database.{{camelCase name}}.create({
48
+ return context.providers.database.{{camelCase name }}.create({
38
49
  data: {
39
50
  {{#each fields}}
40
51
  {{#unless isRelation}}
52
+ {{#unless (or (equals name 'id') (equals name 'createdAt') (equals name 'updatedAt'))}}
41
53
  {{name}}: input.{{name}},
42
54
  {{/unless}}
55
+ {{/unless}}
43
56
  {{/each}}
44
57
  }
45
58
  });
46
59
  },
47
-
48
60
  update: async (params: { id: string } & Update{{pascalCase name}}DTO): Promise<{{pascalCase name}}> => {
49
- const {{camelCase name}} = await context.providers.database.{{camelCase name}}.findUnique({
61
+ const {{camelCase name}} = await context.providers.database.{{lowerCase name }}.findUnique({
50
62
  where: { id: params.id }
51
63
  });
52
-
53
64
  if (!{{camelCase name}}) throw new Error("{{pascalCase name}} not found");
54
-
55
- return context.providers.database.{{camelCase name}}.update({
65
+ return context.providers.database.{{lowerCase name }}.update({
56
66
  where: { id: params.id },
57
67
  data: {
58
68
  {{#each fields}}
59
69
  {{#unless isRelation}}
70
+ {{#unless (or (equals name 'id') (equals name 'createdAt') (equals name 'updatedAt'))}}
60
71
  {{name}}: params.{{name}},
61
72
  {{/unless}}
73
+ {{/unless}}
62
74
  {{/each}}
63
75
  }
64
76
  });
65
77
  },
66
-
67
78
  delete: async (params: { id: string }): Promise<{ id: string }> => {
68
- await context.providers.database.{{camelCase name}}.delete({
79
+ await context.providers.database.{{lowerCase name }}.delete({
69
80
  where: { id: params.id }
70
81
  });
71
-
72
82
  return { id: params.id };
73
83
  }
84
+ {{/if}}
74
85
  }
75
86
  };
76
87
  },
@@ -1,9 +1,11 @@
1
1
  @import "tailwindcss";
2
+ @import "tw-animate-css";
2
3
 
3
- @plugin "tailwindcss-animate";
4
4
  @custom-variant dark (&:is(.dark *));
5
5
 
6
6
  :root {
7
+ --background: oklch(1 0 0);
8
+ --foreground: oklch(0.145 0 0);
7
9
  --card: oklch(1 0 0);
8
10
  --card-foreground: oklch(0.145 0 0);
9
11
  --popover: oklch(1 0 0);
@@ -20,13 +22,13 @@
20
22
  --destructive-foreground: oklch(0.577 0.245 27.325);
21
23
  --border: oklch(0.922 0 0);
22
24
  --input: oklch(0.922 0 0);
23
- --ring: oklch(0.87 0 0);
25
+ --ring: oklch(0.708 0 0);
24
26
  --chart-1: oklch(0.646 0.222 41.116);
25
27
  --chart-2: oklch(0.6 0.118 184.704);
26
28
  --chart-3: oklch(0.398 0.07 227.392);
27
29
  --chart-4: oklch(0.828 0.189 84.429);
28
30
  --chart-5: oklch(0.769 0.188 70.08);
29
- --radius: 1.5rem;
31
+ --radius: 0.625rem;
30
32
  --sidebar: oklch(0.985 0 0);
31
33
  --sidebar-foreground: oklch(0.145 0 0);
32
34
  --sidebar-primary: oklch(0.205 0 0);
@@ -34,9 +36,7 @@
34
36
  --sidebar-accent: oklch(0.97 0 0);
35
37
  --sidebar-accent-foreground: oklch(0.205 0 0);
36
38
  --sidebar-border: oklch(0.922 0 0);
37
- --sidebar-ring: oklch(0.87 0 0);
38
- --background: oklch(1 0 0);
39
- --foreground: oklch(0.145 0 0);
39
+ --sidebar-ring: oklch(0.708 0 0);
40
40
  }
41
41
 
42
42
  .dark {
@@ -74,62 +74,7 @@
74
74
  --sidebar-ring: oklch(0.439 0 0);
75
75
  }
76
76
 
77
- .theme-login-one {
78
- --primary: #ce2a2d;
79
- --primary-foreground: #fff;
80
- --ring: #ce2a2d9c;
81
- --radius: 0rem;
82
- --radius-sm: calc(var(--radius) - 4px);
83
- --radius-md: calc(var(--radius) - 2px);
84
- --radius-lg: var(--radius);
85
-
86
- font-family: var(--font-sans);
87
-
88
- a {
89
- color: var(--primary);
90
- }
91
-
92
- [data-slot="card"] {
93
- border-radius: 0rem;
94
- box-shadow: none;
95
- }
96
- }
97
-
98
- .theme-login-two {
99
- --primary: #035fa8;
100
- --primary-foreground: #fff;
101
- --ring: #035fa89c;
102
- font-family: var(--font-serif);
103
-
104
- a {
105
- color: var(--primary);
106
- }
107
- }
108
-
109
- .theme-login-three {
110
- --primary: #22c55e;
111
- --primary-foreground: #000;
112
- --ring: #22c55e;
113
- --radius: 1.5rem;
114
-
115
- font-family: var(--font-manrope);
116
-
117
- a {
118
- color: var(--primary);
119
- }
120
-
121
- [data-slot="card"] {
122
- @apply shadow-xl;
123
- }
124
-
125
- [data-slot="input"] {
126
- @apply dark:bg-input;
127
- }
128
- }
129
-
130
77
  @theme inline {
131
- --font-sans: var(--font-sans);
132
- --font-mono: var(--font-mono);
133
78
  --color-background: var(--background);
134
79
  --color-foreground: var(--foreground);
135
80
  --color-card: var(--card);
@@ -166,26 +111,6 @@
166
111
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
167
112
  --color-sidebar-border: var(--sidebar-border);
168
113
  --color-sidebar-ring: var(--sidebar-ring);
169
- --animate-accordion-down: accordion-down 0.2s ease-out;
170
- --animate-accordion-up: accordion-up 0.2s ease-out;
171
-
172
- @keyframes accordion-down {
173
- from {
174
- height: 0;
175
- }
176
- to {
177
- height: var(--radix-accordion-content-height);
178
- }
179
- }
180
-
181
- @keyframes accordion-up {
182
- from {
183
- height: var(--radix-accordion-content-height);
184
- }
185
- to {
186
- height: 0;
187
- }
188
- }
189
114
  }
190
115
 
191
116
  @layer base {
@@ -195,4 +120,4 @@
195
120
  body {
196
121
  @apply bg-background text-foreground;
197
122
  }
198
- }
123
+ }
@@ -1,4 +1,4 @@
1
- import { prisma } from "@/core/providers/prisma";
1
+ import { prisma } from "@/providers/prisma";
2
2
 
3
3
  /**
4
4
  * @description Create the context of the application
@@ -2,7 +2,6 @@ import type { Metadata } from "next";
2
2
 
3
3
  import { Geist, Geist_Mono } from "next/font/google";
4
4
  import { IgniterProvider } from '@igniter-js/core/client'
5
- import { ThemeProvider } from "next-themes";
6
5
 
7
6
  import "./globals.css"
8
7
 
@@ -17,8 +16,8 @@ const geistMono = Geist_Mono({
17
16
  });
18
17
 
19
18
  export const metadata: Metadata = {
20
- title: "Igniter Boilerplate",
21
- description: "A customizable boilerplate for Igniter applications",
19
+ title: "Igniter.js Boilerplate",
20
+ description: "A customizable boilerplate for Igniter.js applications",
22
21
  };
23
22
 
24
23
  export default function RootLayout({
@@ -29,18 +28,11 @@ export default function RootLayout({
29
28
  return (
30
29
  <html lang="en" suppressHydrationWarning>
31
30
  <body
32
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
31
+ className={`${geistSans.variable} ${geistMono.variable} antialiased dark`}
33
32
  >
34
- <ThemeProvider
35
- attribute="class"
36
- defaultTheme="system"
37
- enableSystem
38
- disableTransitionOnChange
39
- >
40
- <IgniterProvider>
41
- {children}
42
- </IgniterProvider>
43
- </ThemeProvider>
33
+ <IgniterProvider>
34
+ {children}
35
+ </IgniterProvider>
44
36
  </body>
45
37
  </html>
46
38
  );
@@ -3,14 +3,11 @@
3
3
  import * as React from "react"
4
4
  import Link from "next/link"
5
5
 
6
- import { Badge } from "@/core/design-system/badge"
7
- import { Button } from "@/core/design-system/button"
8
- import { Card, CardContent } from "@/core/design-system/card"
9
- import { ArrowRight, ChevronDown, Code2, Github, Twitter, Youtube } from "lucide-react"
6
+ import { ArrowRight, Code2, Github, Twitter, Youtube } from "lucide-react"
10
7
  import { FileText } from "lucide-react"
11
- import { Moon, Sun } from "lucide-react"
12
- import { useTheme } from "next-themes"
13
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/core/design-system/dropdown-menu"
8
+ import { Badge } from "@/components/ui/badge"
9
+ import { Button } from "@/components/ui/button"
10
+ import { Card, CardContent } from "@/components/ui/card"
14
11
 
15
12
  export default function Home() {
16
13
  return (
@@ -113,38 +110,8 @@ export default function Home() {
113
110
  </a>
114
111
  </p>
115
112
  </nav>
116
-
117
- <ModeToggle />
118
113
  </div>
119
114
  </footer>
120
115
  </div>
121
116
  )
122
117
  }
123
- export function ModeToggle() {
124
- const { setTheme, theme } = useTheme()
125
-
126
- return (
127
- <DropdownMenu>
128
- <DropdownMenuTrigger asChild>
129
- <Button variant="outline" className="space-x-2 flex items-center rounded-full">
130
- <Sun className="h-[1.2rem] w-[1.2rem] transition-all dark:hidden dark:scale-0" />
131
- <Moon className="h-[1.2rem] w-[1.2rem] transition-all hidden dark:flex dark:scale-100" />
132
- <span>{theme === "light" ? "Light" : theme === "dark" ? "Dark" : "System"}</span>
133
-
134
- <ChevronDown className="h-4 w-4 ml-auto" />
135
- </Button>
136
- </DropdownMenuTrigger>
137
- <DropdownMenuContent align="end">
138
- <DropdownMenuItem onClick={() => setTheme("light")}>
139
- Light
140
- </DropdownMenuItem>
141
- <DropdownMenuItem onClick={() => setTheme("dark")}>
142
- Dark
143
- </DropdownMenuItem>
144
- <DropdownMenuItem onClick={() => setTheme("system")}>
145
- System
146
- </DropdownMenuItem>
147
- </DropdownMenuContent>
148
- </DropdownMenu>
149
- )
150
- }
@@ -11,16 +11,13 @@ npm install
11
11
  yarn install
12
12
  ```
13
13
 
14
- 2. **Setup Database**
14
+ 2. **Setup Docker and Database**
15
15
  ```bash
16
16
  # Start Docker containers
17
- npm run docker:up
17
+ docker compose up -d
18
18
 
19
19
  # Generate Prisma Client
20
- npm run db:generate
21
-
22
- # Push database schema
23
- npm run db:push
20
+ npx prisma migrate dev
24
21
  ```
25
22
 
26
23
  3. **Start Development Server**
@@ -36,29 +33,21 @@ Open [http://localhost:3000](http://localhost:3000) to view your application.
36
33
 
37
34
  ```
38
35
  ├── src/
39
- │ ├── app/ # Next.js app directory
40
- │ ├── core/ # Core framework modules
41
- ├── design-system/ # UI components (shadcn/ui)
42
- ├── factories/ # Base factories for features
43
- ├── providers/ # Context providers
44
- │ │ └── utils/ # Shared utilities
45
- │ ├── features/ # Feature modules
46
- │ │ └── [feature]/ # Feature-specific code
47
- │ │ ├── index.ts # Feature entry point
36
+ │ ├── app/ # Next.js app directory (If is Igniter Fullstack APP)
37
+ │ ├── design-system/ # UI components (shadcn/ui) (If is Igniter Fullstack APP)
38
+ │ ├── providers/ # Context providers
39
+ │ ├── utils/ # Shared utilities
40
+ │ ├── features/ # Feature modules
41
+ │ │ └── [feature]/ # Feature-specific code
42
+ ├── index.ts # Feature entry point
48
43
  │ │ ├── controllers/ # HTTP request handlers
49
44
  │ │ │ └── [feature].controller.ts
50
- │ │ ├── services/ # Business logic
51
- │ │ │ └── [feature].service.ts
52
- │ │ ├── repositories/ # Data access
53
- │ │ └── [feature].repository.ts
54
- │ │ ├── validators/ # Data validation schemas
55
- │ │ │ └── [feature].validator.ts
56
- │ │ ├── [feature].types.ts # Feature types
57
- │ │ └── [feature].feature.ts # Feature configuration
45
+ │ │ ├── procedures/ # Business logic
46
+ │ │ │ └── [feature].procedure.ts
47
+ │ │ ├── [feature].interface.ts # Feature types
48
+ │ │ └── [feature].ts # Feature configuration
58
49
  │ └── configs/ # Configuration files
59
50
  ├── public/ # Static files
60
- ├── docs/ # Documentation
61
- ├── scripts/ # Utility scripts
62
51
  └── .github/ # GitHub configuration
63
52
  ```
64
53
 
@@ -68,34 +57,29 @@ Open [http://localhost:3000](http://localhost:3000) to view your application.
68
57
  - `npm run build` - Build for production
69
58
  - `npm run start` - Start production server
70
59
  - `npm run lint` - Run ESLint
71
- - `npm run test` - Run tests with Vitest
72
-
73
- ### Database Scripts
74
- - `npm run docker:up` - Start Docker containers
75
- - `npm run docker:down` - Stop Docker containers
76
- - `npm run db:studio` - Open Prisma Studio
77
- - `npm run db:push` - Push database schema changes
78
- - `npm run db:generate` - Generate Prisma Client
79
60
 
80
61
  ### Igniter CLI Commands
81
- - `npm run igniter:generate feature [name]` - Generate a new feature
62
+ - `npx @igniter-js/cli generate feature [name]` - Generate a new feature
82
63
 
83
64
  ## 🏗 Feature Generation
84
65
  Generate a new feature using the Igniter CLI:
85
66
 
86
67
  ```bash
87
- npm run igniter generate feature
68
+ npx @igniter-js/cli generate feature
88
69
  ```
89
70
 
90
71
  This will create a new feature with the following structure:
91
72
  ```
92
73
  src/features/users/
93
- ├── index.ts # Feature exports
94
- ├── controller.ts # HTTP request handling
95
- ├── service.ts # Business logic
96
- ├── repository.ts # Data access
97
- ├── factory.ts # Object creation
98
- └── schema.ts # Data validation
74
+ ├── features/ # Feature modules
75
+ │ └── [feature]/ # Feature-specific code
76
+ ├── index.ts # Feature entry point
77
+ ├── controllers/ # HTTP request handlers
78
+ │ │ └── [feature].controller.ts
79
+ │ ├── procedures/ # Business logic
80
+ │ │ └── [feature].procedure.ts
81
+ │ ├── [feature].interface.ts # Feature types
82
+ │ └── [feature].ts # Feature configuration
99
83
  ```
100
84
 
101
85
  ## 🧩 Tech Stack
@@ -120,7 +104,6 @@ For more detailed documentation:
120
104
  - [Prisma Documentation](https://www.prisma.io/docs)
121
105
  - [shadcn/ui Documentation](https://ui.shadcn.com)
122
106
  - [Tailwind CSS Documentation](https://tailwindcss.com/docs)
123
- - [Vitest Documentation](https://vitest.dev/docs)
124
107
  - [Zod Documentation](https://zod.dev)
125
108
 
126
109
  ## 🤝 Contributing
@@ -0,0 +1,39 @@
1
+ import { zodResolver } from "@hookform/resolvers/zod";
2
+ import { useEffect, useRef } from "react";
3
+ import { useForm } from "react-hook-form";
4
+ import type { z } from "zod";
5
+
6
+ interface UseFormOptions<T extends z.ZodType> {
7
+ schema: T;
8
+ defaultValues?: z.infer<T>;
9
+ onSubmit?: (values: z.infer<T>) => void;
10
+ }
11
+
12
+ export function useFormWithZod<T extends z.ZodType>({
13
+ schema,
14
+ defaultValues,
15
+ onSubmit,
16
+ ...rest
17
+ }: UseFormOptions<T>) {
18
+ const form = useForm<z.infer<T>>({
19
+ resolver: zodResolver(schema),
20
+ defaultValues,
21
+ ...rest,
22
+ });
23
+
24
+ const prevDefaultValuesRef = useRef(defaultValues);
25
+
26
+ useEffect(() => {
27
+ const isDefaultValuesDifferent = JSON.stringify(prevDefaultValuesRef.current) !== JSON.stringify(defaultValues);
28
+
29
+ if (defaultValues && isDefaultValuesDifferent) {
30
+ prevDefaultValuesRef.current = defaultValues;
31
+ form.reset(defaultValues);
32
+ }
33
+ }, [defaultValues, form]);
34
+
35
+ return {
36
+ ...form,
37
+ onSubmit: form.handleSubmit(onSubmit || (() => {})),
38
+ };
39
+ }