swallowkit 1.0.0-beta.2 → 1.0.0-beta.20

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +312 -215
  3. package/README.md +369 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +35 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +292 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +19 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +476 -117
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +13 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2627 -1708
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +3 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +617 -129
  39. package/dist/cli/commands/scaffold.js.map +1 -1
  40. package/dist/cli/index.d.ts +4 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +162 -42
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +8 -2
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +90 -4
  47. package/dist/core/config.js.map +1 -1
  48. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  49. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  50. package/dist/core/mock/connector-mock-server.js +480 -0
  51. package/dist/core/mock/connector-mock-server.js.map +1 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  53. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  54. package/dist/core/mock/zod-mock-generator.js +163 -0
  55. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  56. package/dist/core/operations/create-model.d.ts +15 -0
  57. package/dist/core/operations/create-model.d.ts.map +1 -0
  58. package/dist/core/operations/create-model.js +171 -0
  59. package/dist/core/operations/create-model.js.map +1 -0
  60. package/dist/core/operations/runtime.d.ts +32 -0
  61. package/dist/core/operations/runtime.d.ts.map +1 -0
  62. package/dist/core/operations/runtime.js +225 -0
  63. package/dist/core/operations/runtime.js.map +1 -0
  64. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  65. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  66. package/dist/core/operations/scaffold-machine.js +63 -0
  67. package/dist/core/operations/scaffold-machine.js.map +1 -0
  68. package/dist/core/project/manifest.d.ts +92 -0
  69. package/dist/core/project/manifest.d.ts.map +1 -0
  70. package/dist/core/project/manifest.js +321 -0
  71. package/dist/core/project/manifest.js.map +1 -0
  72. package/dist/core/project/validation.d.ts +20 -0
  73. package/dist/core/project/validation.d.ts.map +1 -0
  74. package/dist/core/project/validation.js +204 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1244 -0
  79. package/dist/core/scaffold/auth-generator.js.map +1 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  81. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  82. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  83. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  84. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  85. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  86. package/dist/core/scaffold/functions-generator.js +920 -213
  87. package/dist/core/scaffold/functions-generator.js.map +1 -1
  88. package/dist/core/scaffold/model-parser.d.ts +20 -1
  89. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  90. package/dist/core/scaffold/model-parser.js +329 -135
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  93. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  94. package/dist/core/scaffold/nextjs-generator.js +314 -182
  95. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  96. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  97. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  98. package/dist/core/scaffold/openapi-generator.js +190 -0
  99. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  100. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  101. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  102. package/dist/core/scaffold/ui-generator.js +768 -663
  103. package/dist/core/scaffold/ui-generator.js.map +1 -1
  104. package/dist/database/base-model.d.ts +3 -3
  105. package/dist/database/base-model.js +3 -3
  106. package/dist/index.d.ts +2 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +2 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/machine/contracts.d.ts +16 -0
  111. package/dist/machine/contracts.d.ts.map +1 -0
  112. package/dist/machine/contracts.js +3 -0
  113. package/dist/machine/contracts.js.map +1 -0
  114. package/dist/machine/errors.d.ts +11 -0
  115. package/dist/machine/errors.d.ts.map +1 -0
  116. package/dist/machine/errors.js +34 -0
  117. package/dist/machine/errors.js.map +1 -0
  118. package/dist/machine/index.d.ts +3 -0
  119. package/dist/machine/index.d.ts.map +1 -0
  120. package/dist/machine/index.js +156 -0
  121. package/dist/machine/index.js.map +1 -0
  122. package/dist/mcp/index.d.ts +25 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +184 -0
  125. package/dist/mcp/index.js.map +1 -0
  126. package/dist/types/index.d.ts +65 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/utils/package-manager.d.ts +109 -0
  129. package/dist/utils/package-manager.d.ts.map +1 -0
  130. package/dist/utils/package-manager.js +215 -0
  131. package/dist/utils/package-manager.js.map +1 -0
  132. package/package.json +85 -73
  133. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  134. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  135. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  136. package/src/__tests__/auth.test.ts +654 -0
  137. package/src/__tests__/config.test.ts +263 -0
  138. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  139. package/src/__tests__/connector-mock-server.test.ts +439 -0
  140. package/src/__tests__/connector-model-bff.test.ts +162 -0
  141. package/src/__tests__/dev-seeds.test.ts +112 -0
  142. package/src/__tests__/dev.test.ts +148 -0
  143. package/src/__tests__/fixtures.ts +144 -0
  144. package/src/__tests__/functions-generator.test.ts +237 -0
  145. package/src/__tests__/init.test.ts +80 -0
  146. package/src/__tests__/machine.test.ts +212 -0
  147. package/src/__tests__/mcp.test.ts +56 -0
  148. package/src/__tests__/model-parser.test.ts +72 -0
  149. package/src/__tests__/nextjs-generator.test.ts +97 -0
  150. package/src/__tests__/openapi-generator.test.ts +43 -0
  151. package/src/__tests__/package-manager.test.ts +189 -0
  152. package/src/__tests__/scaffold.test.ts +39 -0
  153. package/src/__tests__/string-utils.test.ts +75 -0
  154. package/src/__tests__/ui-generator.test.ts +144 -0
  155. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  156. package/src/cli/commands/add-auth.ts +500 -0
  157. package/src/cli/commands/add-connector.ts +158 -0
  158. package/src/cli/commands/create-model.ts +62 -0
  159. package/src/cli/commands/dev-seeds.ts +358 -0
  160. package/src/cli/commands/dev.ts +962 -0
  161. package/src/cli/commands/index.ts +9 -0
  162. package/src/cli/commands/init.ts +3371 -0
  163. package/src/cli/commands/provision.ts +193 -0
  164. package/src/cli/commands/scaffold.ts +1211 -0
  165. package/src/cli/index.ts +191 -0
  166. package/src/core/config.ts +308 -0
  167. package/src/core/mock/connector-mock-server.ts +555 -0
  168. package/src/core/mock/zod-mock-generator.ts +205 -0
  169. package/src/core/operations/create-model.ts +174 -0
  170. package/src/core/operations/runtime.ts +235 -0
  171. package/src/core/operations/scaffold-machine.ts +91 -0
  172. package/src/core/project/manifest.ts +402 -0
  173. package/src/core/project/validation.ts +221 -0
  174. package/src/core/scaffold/auth-generator.ts +1284 -0
  175. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  176. package/src/core/scaffold/functions-generator.ts +970 -0
  177. package/src/core/scaffold/model-parser.ts +841 -0
  178. package/src/core/scaffold/nextjs-generator.ts +370 -0
  179. package/src/core/scaffold/openapi-generator.ts +212 -0
  180. package/src/core/scaffold/ui-generator.ts +1061 -0
  181. package/src/database/base-model.ts +184 -0
  182. package/src/database/client.ts +140 -0
  183. package/src/database/repository.ts +104 -0
  184. package/src/database/runtime-check.ts +25 -0
  185. package/src/index.ts +27 -0
  186. package/src/machine/contracts.ts +17 -0
  187. package/src/machine/errors.ts +34 -0
  188. package/src/machine/index.ts +173 -0
  189. package/src/mcp/index.ts +185 -0
  190. package/src/types/index.ts +134 -0
  191. package/src/utils/package-manager.ts +229 -0
@@ -0,0 +1,532 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`generateDetailPage generates detail page (snapshot) 1`] = `
4
+ "'use client';
5
+
6
+ import { useEffect, useState } from 'react';
7
+ import { useParams, useRouter } from 'next/navigation';
8
+ import Link from 'next/link';
9
+ import { z } from 'zod/v4';
10
+ import { todoSchema } from '@myapp/shared';
11
+
12
+
13
+ type Todo = z.infer<typeof todoSchema>;
14
+
15
+ export default function TodoDetailPage() {
16
+ const params = useParams();
17
+ const router = useRouter();
18
+ const [todo, setTodo] = useState<Todo | null>(null);
19
+ const [loading, setLoading] = useState(true);
20
+ const [error, setError] = useState<string | null>(null);
21
+
22
+
23
+
24
+ useEffect(() => {
25
+ const id = params?.id as string;
26
+ if (!id) return;
27
+
28
+ fetch(\`/api/todo/\${id}\`)
29
+ .then((res) => {
30
+ if (!res.ok) throw new Error('Failed to fetch todo');
31
+ return res.json();
32
+ })
33
+ .then((data) => {
34
+ setTodo(data);
35
+ setLoading(false);
36
+ })
37
+ .catch((err) => {
38
+ setError(err.message);
39
+ setLoading(false);
40
+ });
41
+
42
+
43
+ }, [params]);
44
+
45
+ const handleDelete = async () => {
46
+ if (!confirm('Are you sure you want to delete this item?')) return;
47
+
48
+ try {
49
+ const res = await fetch(\`/api/todo/\${params?.id}\`, {
50
+ method: 'DELETE',
51
+ });
52
+
53
+ if (!res.ok) throw new Error('Failed to delete todo');
54
+
55
+ router.push('/todo');
56
+ } catch (err: any) {
57
+ alert(\`Error: \${err.message}\`);
58
+ }
59
+ };
60
+
61
+ if (loading) {
62
+ return (
63
+ <div className="flex items-center justify-center min-h-screen">
64
+ <div className="text-lg text-gray-900 dark:text-gray-100">Loading...</div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ if (error || !todo) {
70
+ return (
71
+ <div className="flex items-center justify-center min-h-screen">
72
+ <div className="text-red-600 dark:text-red-400">Error: {error || 'Todo not found'}</div>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ return (
78
+ <div className="container mx-auto px-4 py-8">
79
+ <div className="max-w-2xl mx-auto">
80
+ <div className="flex justify-between items-center mb-6">
81
+ <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Todo Details</h1>
82
+ <div className="space-x-2">
83
+ <Link
84
+ href={\`/todo/\${todo.id}/edit\`}
85
+ className="inline-flex items-center bg-green-600 hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600 text-white px-4 py-2 rounded"
86
+ >
87
+ Edit
88
+ </Link>
89
+ <button
90
+ onClick={handleDelete}
91
+ className="inline-flex items-center bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white px-4 py-2 rounded"
92
+ >
93
+ Delete
94
+ </button>
95
+ </div>
96
+ </div>
97
+
98
+ <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
99
+ <dl className="space-y-4">
100
+ <div>
101
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">id</dt>
102
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.id)}</dd>
103
+ </div>
104
+ <div>
105
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">title</dt>
106
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.title)}</dd>
107
+ </div>
108
+ <div>
109
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">description</dt>
110
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.description)}</dd>
111
+ </div>
112
+ <div>
113
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">completed</dt>
114
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.completed)}</dd>
115
+ </div>
116
+ <div>
117
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">createdAt</dt>
118
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.createdAt)}</dd>
119
+ </div>
120
+ <div>
121
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">updatedAt</dt>
122
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{String(todo.updatedAt)}</dd>
123
+ </div>
124
+ </dl>
125
+ </div>
126
+
127
+ <div className="mt-6">
128
+ <Link
129
+ href="/todo"
130
+ className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300"
131
+ >
132
+ &larr; Back to list
133
+ </Link>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
139
+ "
140
+ `;
141
+
142
+ exports[`generateEditPage generates edit page (snapshot) 1`] = `
143
+ "'use client';
144
+
145
+ import { useEffect, useState } from 'react';
146
+ import { useParams, useRouter } from 'next/navigation';
147
+ import TodoForm from '../../_components/TodoForm';
148
+ import { z } from 'zod/v4';
149
+ import { todoSchema } from '@myapp/shared';
150
+
151
+
152
+ type Todo = z.infer<typeof todoSchema>;
153
+
154
+ export default function EditTodoPage() {
155
+ const params = useParams();
156
+
157
+ const [todo, setTodo] = useState<Todo | null>(null);
158
+ const [loading, setLoading] = useState(true);
159
+
160
+
161
+
162
+ useEffect(() => {
163
+ const id = params?.id as string;
164
+ if (!id) return;
165
+
166
+ fetch(\`/api/todo/\${id}\`)
167
+ .then((res) => res.json())
168
+ .then((data) => {
169
+ setTodo(data);
170
+ setLoading(false);
171
+ })
172
+ .catch(() => setLoading(false));
173
+ }, [params]);
174
+
175
+ if (loading) {
176
+ return (
177
+ <div className="flex items-center justify-center min-h-screen">
178
+ <div className="text-lg">Loading...</div>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ if (!todo) {
184
+ return (
185
+ <div className="flex items-center justify-center min-h-screen">
186
+ <div className="text-red-600">Todo not found</div>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ return (
192
+ <div className="container mx-auto px-4 py-8">
193
+ <div className="max-w-2xl mx-auto">
194
+ <h1 className="text-3xl font-bold mb-6">Edit Todo</h1>
195
+ <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
196
+ <TodoForm initialData={todo} isEdit={true} />
197
+ </div>
198
+ </div>
199
+ </div>
200
+ );
201
+ }
202
+ "
203
+ `;
204
+
205
+ exports[`generateFormComponent generates form component (snapshot) 1`] = `
206
+ "'use client';
207
+
208
+ import { useState, FormEvent } from 'react';
209
+ import { useRouter } from 'next/navigation';
210
+ import { z } from 'zod/v4';
211
+ import { todoSchema } from '@myapp/shared';
212
+
213
+ // Input schema: SwallowKit-managed fields (id, createdAt, updatedAt) are optional
214
+ // These fields are ignored by the backend and auto-managed
215
+ const TodoInputSchema = todoSchema.partial({ id: true, createdAt: true, updatedAt: true });
216
+
217
+ type Todo = z.infer<typeof todoSchema>;
218
+
219
+ interface TodoFormProps {
220
+ initialData?: Todo;
221
+ isEdit?: boolean;
222
+ }
223
+
224
+ export default function TodoForm({ initialData, isEdit = false }: TodoFormProps) {
225
+ const router = useRouter();
226
+ const [loading, setLoading] = useState(false);
227
+ const [errors, setErrors] = useState<Record<string, string>>({});
228
+
229
+
230
+
231
+ const [formData, setFormData] = useState({
232
+ title: initialData?.title ?? '',
233
+ description: initialData?.description ?? '',
234
+ completed: initialData?.completed ?? false,
235
+ });
236
+
237
+ const handleSubmit = async (e: FormEvent) => {
238
+ e.preventDefault();
239
+ setLoading(true);
240
+ setErrors({});
241
+
242
+ try {
243
+ // Array フィールドをカンマ区切りから配列に変換
244
+ const submitData: any = { ...formData };
245
+
246
+
247
+
248
+
249
+
250
+ // Validate input (excluding SwallowKit-managed fields)
251
+ TodoInputSchema.parse(submitData);
252
+
253
+ const url = isEdit ? \`/api/todo/\${initialData!.id}\` : '/api/todo';
254
+ const method = isEdit ? 'PUT' : 'POST';
255
+
256
+ const res = await fetch(url, {
257
+ method,
258
+ headers: { 'Content-Type': 'application/json' },
259
+ body: JSON.stringify(submitData),
260
+ });
261
+
262
+ if (!res.ok) {
263
+ const errorData = await res.json();
264
+ throw new Error(errorData.error || 'Failed to save todo');
265
+ }
266
+
267
+ router.push('/todo');
268
+ } catch (err: any) {
269
+ if (err.issues) {
270
+ // Zod validation errors
271
+ const fieldErrors: Record<string, string> = {};
272
+ err.issues.forEach((error: any) => {
273
+ const field = error.path[0];
274
+ fieldErrors[field] = error.message;
275
+ });
276
+ setErrors(fieldErrors);
277
+ } else {
278
+ alert(\`Error: \${err.message}\`);
279
+ }
280
+ setLoading(false);
281
+ }
282
+ };
283
+
284
+ return (
285
+ <form onSubmit={handleSubmit} className="space-y-6">
286
+ <div>
287
+ <label htmlFor="title" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
288
+ title *
289
+ </label>
290
+ <input
291
+ type="text"
292
+ id="title"
293
+ name="title"
294
+ value={formData.title}
295
+ onChange={(e) => setFormData({ ...formData, title: e.target.value })}
296
+ className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm px-3 py-2 focus:border-blue-500 focus:ring-blue-500 dark:focus:border-blue-400 dark:focus:ring-blue-400"
297
+ required
298
+ />
299
+ {errors.title && (
300
+ <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.title}</p>
301
+ )}
302
+ </div>
303
+
304
+ <div>
305
+ <label htmlFor="description" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
306
+ description
307
+ </label>
308
+ <input
309
+ type="text"
310
+ id="description"
311
+ name="description"
312
+ value={formData.description}
313
+ onChange={(e) => setFormData({ ...formData, description: e.target.value })}
314
+ className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm px-3 py-2 focus:border-blue-500 focus:ring-blue-500 dark:focus:border-blue-400 dark:focus:ring-blue-400"
315
+
316
+ />
317
+ {errors.description && (
318
+ <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.description}</p>
319
+ )}
320
+ </div>
321
+
322
+ <div className="flex items-center">
323
+ <input
324
+ type="checkbox"
325
+ id="completed"
326
+ name="completed"
327
+ checked={formData.completed}
328
+ onChange={(e) => setFormData({ ...formData, completed: e.target.checked })}
329
+ className="h-4 w-4 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 border border-gray-300 dark:border-gray-600 rounded"
330
+ />
331
+ <label htmlFor="completed" className="ml-2 block text-sm text-gray-700 dark:text-gray-300">
332
+ completed
333
+ </label>
334
+ {errors.completed && (
335
+ <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.completed}</p>
336
+ )}
337
+ </div>
338
+
339
+ <div className="flex gap-4">
340
+ <button
341
+ type="submit"
342
+ disabled={loading}
343
+ className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded disabled:opacity-50"
344
+ >
345
+ {loading ? 'Saving...' : isEdit ? 'Update' : 'Create'}
346
+ </button>
347
+ <button
348
+ type="button"
349
+ onClick={() => router.push('/todo')}
350
+ className="bg-gray-300 hover:bg-gray-400 text-gray-800 px-4 py-2 rounded"
351
+ >
352
+ Cancel
353
+ </button>
354
+ </div>
355
+ </form>
356
+ );
357
+ }
358
+ "
359
+ `;
360
+
361
+ exports[`generateListPage generates list page for basic model (snapshot) 1`] = `
362
+ "'use client';
363
+
364
+ import { useEffect, useState } from 'react';
365
+ import Link from 'next/link';
366
+ import { z } from 'zod/v4';
367
+ import { todoSchema } from '@myapp/shared';
368
+
369
+
370
+ type Todo = z.infer<typeof todoSchema>;
371
+
372
+ export default function TodoListPage() {
373
+ const [todos, setTodos] = useState<Todo[]>([]);
374
+ const [loading, setLoading] = useState(true);
375
+ const [error, setError] = useState<string | null>(null);
376
+
377
+
378
+
379
+ useEffect(() => {
380
+ fetch('/api/todo')
381
+ .then((res) => {
382
+ if (!res.ok) throw new Error('Failed to fetch todos');
383
+ return res.json();
384
+ })
385
+ .then((data) => {
386
+ setTodos(data);
387
+ setLoading(false);
388
+ })
389
+ .catch((err) => {
390
+ setError(err.message);
391
+ setLoading(false);
392
+ });
393
+
394
+
395
+ }, []);
396
+
397
+ const handleDelete = async (id: string) => {
398
+ if (!confirm('Are you sure you want to delete this item?')) return;
399
+
400
+ try {
401
+ const res = await fetch(\`/api/todo/\${id}\`, {
402
+ method: 'DELETE',
403
+ });
404
+
405
+ if (!res.ok) throw new Error('Failed to delete todo');
406
+
407
+ setTodos(todos.filter((item) => item.id !== id));
408
+ } catch (err: any) {
409
+ alert(\`Error: \${err.message}\`);
410
+ }
411
+ };
412
+
413
+ if (loading) {
414
+ return (
415
+ <div className="flex items-center justify-center min-h-screen">
416
+ <div className="text-lg text-gray-900 dark:text-gray-100">Loading...</div>
417
+ </div>
418
+ );
419
+ }
420
+
421
+ if (error) {
422
+ return (
423
+ <div className="flex items-center justify-center min-h-screen">
424
+ <div className="text-red-600 dark:text-red-400">Error: {error}</div>
425
+ </div>
426
+ );
427
+ }
428
+
429
+ return (
430
+ <div className="container mx-auto px-4 py-8">
431
+ <div className="mb-4">
432
+ <Link
433
+ href="/"
434
+ className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 text-sm"
435
+ >
436
+ ← Home
437
+ </Link>
438
+ </div>
439
+ <div className="flex justify-between items-center mb-6">
440
+ <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Todo</h1>
441
+ <Link
442
+ href="/todo/new"
443
+ className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded"
444
+ >
445
+ Create New
446
+ </Link>
447
+ </div>
448
+
449
+ {todos.length === 0 ? (
450
+ <div className="text-center py-12 text-gray-500 dark:text-gray-400">
451
+ No todos found. Create your first one!
452
+ </div>
453
+ ) : (
454
+ <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg overflow-hidden">
455
+ <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
456
+ <thead className="bg-gray-50 dark:bg-gray-900">
457
+ <tr>
458
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
459
+ title
460
+ </th>
461
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
462
+ description
463
+ </th>
464
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
465
+ completed
466
+ </th>
467
+ <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
468
+ Actions
469
+ </th>
470
+ </tr>
471
+ </thead>
472
+ <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
473
+ {todos.map((item) => (
474
+ <tr key={item.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
475
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
476
+ {String(item.title)}
477
+ </td>
478
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
479
+ {String(item.description)}
480
+ </td>
481
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
482
+ {String(item.completed)}
483
+ </td>
484
+ <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
485
+ <Link
486
+ href={\`/todo/\${item.id}\`}
487
+ className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 mr-4"
488
+ >
489
+ View
490
+ </Link>
491
+ <Link
492
+ href={\`/todo/\${item.id}/edit\`}
493
+ className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 mr-4"
494
+ >
495
+ Edit
496
+ </Link>
497
+ <button
498
+ onClick={() => handleDelete(item.id)}
499
+ className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
500
+ >
501
+ Delete
502
+ </button>
503
+ </td>
504
+ </tr>
505
+ ))}
506
+ </tbody>
507
+ </table>
508
+ </div>
509
+ )}
510
+ </div>
511
+ );
512
+ }
513
+ "
514
+ `;
515
+
516
+ exports[`generateNewPage generates new page (snapshot) 1`] = `
517
+ "import TodoForm from '../_components/TodoForm';
518
+
519
+ export default function NewTodoPage() {
520
+ return (
521
+ <div className="container mx-auto px-4 py-8">
522
+ <div className="max-w-2xl mx-auto">
523
+ <h1 className="text-3xl font-bold mb-6">Create New Todo</h1>
524
+ <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
525
+ <TodoForm />
526
+ </div>
527
+ </div>
528
+ </div>
529
+ );
530
+ }
531
+ "
532
+ `;