swallowkit 1.0.0-beta.9 → 1.0.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 (272) hide show
  1. package/README.ja.md +87 -181
  2. package/README.md +87 -238
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +27 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
  25. package/dist/cli/commands/dev-seeds.js +188 -6
  26. package/dist/cli/commands/dev-seeds.js.map +1 -1
  27. package/dist/cli/commands/dev.d.ts +30 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +495 -129
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/init.d.ts +69 -1
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +788 -766
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/scaffold.d.ts +24 -3
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +408 -189
  38. package/dist/cli/commands/scaffold.js.map +1 -1
  39. package/dist/cli/index.d.ts +5 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +335 -58
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +13 -3
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +151 -8
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  48. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  49. package/dist/core/mock/connector-mock-server.js +480 -0
  50. package/dist/core/mock/connector-mock-server.js.map +1 -0
  51. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  53. package/dist/core/mock/zod-mock-generator.js +163 -0
  54. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  55. package/dist/core/operations/auth-operations.d.ts +61 -0
  56. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  57. package/dist/core/operations/auth-operations.js +272 -0
  58. package/dist/core/operations/auth-operations.js.map +1 -0
  59. package/dist/core/operations/create-model.d.ts +15 -0
  60. package/dist/core/operations/create-model.d.ts.map +1 -0
  61. package/dist/core/operations/create-model.js +185 -0
  62. package/dist/core/operations/create-model.js.map +1 -0
  63. package/dist/core/operations/file-session.d.ts +56 -0
  64. package/dist/core/operations/file-session.d.ts.map +1 -0
  65. package/dist/core/operations/file-session.js +203 -0
  66. package/dist/core/operations/file-session.js.map +1 -0
  67. package/dist/core/operations/provision-operations.d.ts +52 -0
  68. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  69. package/dist/core/operations/provision-operations.js +211 -0
  70. package/dist/core/operations/provision-operations.js.map +1 -0
  71. package/dist/core/operations/runtime.d.ts +37 -0
  72. package/dist/core/operations/runtime.d.ts.map +1 -0
  73. package/dist/core/operations/runtime.js +248 -0
  74. package/dist/core/operations/runtime.js.map +1 -0
  75. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  76. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  77. package/dist/core/operations/scaffold-apply.js +57 -0
  78. package/dist/core/operations/scaffold-apply.js.map +1 -0
  79. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  80. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-machine.js +51 -0
  82. package/dist/core/operations/scaffold-machine.js.map +1 -0
  83. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  84. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-plan.js +187 -0
  86. package/dist/core/operations/scaffold-plan.js.map +1 -0
  87. package/dist/core/project/agent-instructions.d.ts +29 -0
  88. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  89. package/dist/core/project/agent-instructions.js +160 -0
  90. package/dist/core/project/agent-instructions.js.map +1 -0
  91. package/dist/core/project/artifacts.d.ts +58 -0
  92. package/dist/core/project/artifacts.d.ts.map +1 -0
  93. package/dist/core/project/artifacts.js +179 -0
  94. package/dist/core/project/artifacts.js.map +1 -0
  95. package/dist/core/project/boundaries.d.ts +50 -0
  96. package/dist/core/project/boundaries.d.ts.map +1 -0
  97. package/dist/core/project/boundaries.js +117 -0
  98. package/dist/core/project/boundaries.js.map +1 -0
  99. package/dist/core/project/capabilities.d.ts +58 -0
  100. package/dist/core/project/capabilities.d.ts.map +1 -0
  101. package/dist/core/project/capabilities.js +135 -0
  102. package/dist/core/project/capabilities.js.map +1 -0
  103. package/dist/core/project/drift.d.ts +29 -0
  104. package/dist/core/project/drift.d.ts.map +1 -0
  105. package/dist/core/project/drift.js +188 -0
  106. package/dist/core/project/drift.js.map +1 -0
  107. package/dist/core/project/infra.d.ts +49 -0
  108. package/dist/core/project/infra.d.ts.map +1 -0
  109. package/dist/core/project/infra.js +167 -0
  110. package/dist/core/project/infra.js.map +1 -0
  111. package/dist/core/project/manifest.d.ts +92 -0
  112. package/dist/core/project/manifest.d.ts.map +1 -0
  113. package/dist/core/project/manifest.js +321 -0
  114. package/dist/core/project/manifest.js.map +1 -0
  115. package/dist/core/project/state.d.ts +15 -0
  116. package/dist/core/project/state.d.ts.map +1 -0
  117. package/dist/core/project/state.js +106 -0
  118. package/dist/core/project/state.js.map +1 -0
  119. package/dist/core/project/validation.d.ts +20 -0
  120. package/dist/core/project/validation.d.ts.map +1 -0
  121. package/dist/core/project/validation.js +274 -0
  122. package/dist/core/project/validation.js.map +1 -0
  123. package/dist/core/project/workflows.d.ts +43 -0
  124. package/dist/core/project/workflows.d.ts.map +1 -0
  125. package/dist/core/project/workflows.js +299 -0
  126. package/dist/core/project/workflows.js.map +1 -0
  127. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  128. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  129. package/dist/core/scaffold/auth-generator.js +1774 -0
  130. package/dist/core/scaffold/auth-generator.js.map +1 -0
  131. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  132. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  134. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  135. package/dist/core/scaffold/functions-generator.d.ts +4 -3
  136. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  137. package/dist/core/scaffold/functions-generator.js +405 -134
  138. package/dist/core/scaffold/functions-generator.js.map +1 -1
  139. package/dist/core/scaffold/model-parser.d.ts +20 -0
  140. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  141. package/dist/core/scaffold/model-parser.js +229 -78
  142. package/dist/core/scaffold/model-parser.js.map +1 -1
  143. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  144. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  145. package/dist/core/scaffold/native-schema-generator.js +753 -0
  146. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  147. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  148. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  149. package/dist/core/scaffold/nextjs-generator.js +152 -8
  150. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  151. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  152. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/ui-generator.js +174 -34
  154. package/dist/core/scaffold/ui-generator.js.map +1 -1
  155. package/dist/core/verify/index.d.ts +69 -0
  156. package/dist/core/verify/index.d.ts.map +1 -0
  157. package/dist/core/verify/index.js +412 -0
  158. package/dist/core/verify/index.js.map +1 -0
  159. package/dist/machine/contracts.d.ts +24 -0
  160. package/dist/machine/contracts.d.ts.map +1 -0
  161. package/dist/machine/contracts.js +3 -0
  162. package/dist/machine/contracts.js.map +1 -0
  163. package/dist/machine/errors.d.ts +14 -0
  164. package/dist/machine/errors.d.ts.map +1 -0
  165. package/dist/machine/errors.js +42 -0
  166. package/dist/machine/errors.js.map +1 -0
  167. package/dist/machine/index.d.ts +3 -0
  168. package/dist/machine/index.d.ts.map +1 -0
  169. package/dist/machine/index.js +566 -0
  170. package/dist/machine/index.js.map +1 -0
  171. package/dist/mcp/index.d.ts +25 -0
  172. package/dist/mcp/index.d.ts.map +1 -0
  173. package/dist/mcp/index.js +425 -0
  174. package/dist/mcp/index.js.map +1 -0
  175. package/dist/types/index.d.ts +105 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/package-manager.d.ts +7 -11
  178. package/dist/utils/package-manager.d.ts.map +1 -1
  179. package/dist/utils/package-manager.js +42 -17
  180. package/dist/utils/package-manager.js.map +1 -1
  181. package/dist/utils/python-uv.d.ts +21 -0
  182. package/dist/utils/python-uv.d.ts.map +1 -0
  183. package/dist/utils/python-uv.js +111 -0
  184. package/dist/utils/python-uv.js.map +1 -0
  185. package/dist/version.d.ts +2 -0
  186. package/dist/version.d.ts.map +1 -0
  187. package/dist/version.js +49 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +15 -17
  190. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
  191. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
  192. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
  193. package/src/__tests__/agent-instructions.test.ts +237 -0
  194. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  195. package/src/__tests__/auth.test.ts +770 -0
  196. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  197. package/src/__tests__/config.test.ts +176 -3
  198. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  199. package/src/__tests__/connector-mock-server.test.ts +459 -0
  200. package/src/__tests__/connector-model-bff.test.ts +162 -0
  201. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  202. package/src/__tests__/dev-seeds.test.ts +124 -0
  203. package/src/__tests__/dev.test.ts +254 -0
  204. package/src/__tests__/file-session.test.ts +98 -0
  205. package/src/__tests__/fixtures.ts +101 -0
  206. package/src/__tests__/functions-generator.test.ts +137 -1
  207. package/src/__tests__/init.test.ts +366 -2
  208. package/src/__tests__/jest-globals.d.ts +8 -0
  209. package/src/__tests__/machine.test.ts +371 -0
  210. package/src/__tests__/mcp.test.ts +253 -0
  211. package/src/__tests__/model-parser.test.ts +142 -0
  212. package/src/__tests__/multi-auth.test.ts +160 -0
  213. package/src/__tests__/nextjs-generator.test.ts +8 -0
  214. package/src/__tests__/package-manager.test.ts +59 -4
  215. package/src/__tests__/plan-apply.test.ts +499 -0
  216. package/src/__tests__/python-uv.test.ts +48 -0
  217. package/src/__tests__/scaffold.test.ts +287 -24
  218. package/src/__tests__/tsconfig.json +15 -0
  219. package/src/__tests__/ui-generator.test.ts +104 -0
  220. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  221. package/src/cli/commands/add-auth.ts +853 -0
  222. package/src/cli/commands/add-connector.ts +159 -0
  223. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  224. package/src/cli/commands/create-model.ts +21 -100
  225. package/src/cli/commands/dev-seeds.ts +278 -7
  226. package/src/cli/commands/dev.ts +607 -160
  227. package/src/cli/commands/init.ts +851 -786
  228. package/src/cli/commands/scaffold.ts +531 -236
  229. package/src/cli/index.ts +338 -60
  230. package/src/core/config.ts +145 -9
  231. package/src/core/mock/connector-mock-server.ts +555 -0
  232. package/src/core/mock/zod-mock-generator.ts +205 -0
  233. package/src/core/operations/auth-operations.ts +360 -0
  234. package/src/core/operations/create-model.ts +191 -0
  235. package/src/core/operations/file-session.ts +222 -0
  236. package/src/core/operations/provision-operations.ts +297 -0
  237. package/src/core/operations/runtime.ts +258 -0
  238. package/src/core/operations/scaffold-apply.ts +106 -0
  239. package/src/core/operations/scaffold-machine.ts +77 -0
  240. package/src/core/operations/scaffold-plan.ts +215 -0
  241. package/src/core/project/agent-instructions.ts +167 -0
  242. package/src/core/project/agent-templates/AGENTS.md +504 -0
  243. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  244. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  245. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  246. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  247. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  248. package/src/core/project/artifacts.ts +205 -0
  249. package/src/core/project/boundaries.ts +170 -0
  250. package/src/core/project/capabilities.ts +193 -0
  251. package/src/core/project/drift.ts +202 -0
  252. package/src/core/project/infra.ts +196 -0
  253. package/src/core/project/manifest.ts +402 -0
  254. package/src/core/project/state.ts +77 -0
  255. package/src/core/project/validation.ts +295 -0
  256. package/src/core/project/workflows.ts +295 -0
  257. package/src/core/scaffold/auth-generator.ts +1824 -0
  258. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  259. package/src/core/scaffold/functions-generator.ts +432 -134
  260. package/src/core/scaffold/model-parser.ts +244 -90
  261. package/src/core/scaffold/native-schema-generator.ts +904 -0
  262. package/src/core/scaffold/nextjs-generator.ts +174 -9
  263. package/src/core/scaffold/ui-generator.ts +191 -36
  264. package/src/core/verify/index.ts +468 -0
  265. package/src/machine/contracts.ts +33 -0
  266. package/src/machine/errors.ts +45 -0
  267. package/src/machine/index.ts +690 -0
  268. package/src/mcp/index.ts +423 -0
  269. package/src/types/index.ts +137 -0
  270. package/src/utils/package-manager.ts +41 -19
  271. package/src/utils/python-uv.ts +96 -0
  272. package/src/version.ts +13 -0
@@ -3,16 +3,52 @@
3
3
  * CRUD 画面(一覧、詳細、新規作成、編集)を生成する
4
4
  */
5
5
 
6
- import { ModelInfo, FieldInfo, toCamelCase, toKebabCase, toPascalCase } from "./model-parser";
6
+ import { ModelInfo, toCamelCase, toKebabCase } from "./model-parser";
7
+ import { ModelAuthPolicy } from "../../types";
8
+
9
+ /** auth が有効かつ authPolicy が存在する場合に渡されるオプション */
10
+ export interface UIAuthOptions {
11
+ /** モデル固有のロールポリシー */
12
+ authPolicy: ModelAuthPolicy;
13
+ writeRoles?: string[];
14
+ authenticatedFetchImport?: string;
15
+ authContextImport?: string;
16
+ }
17
+
18
+ /**
19
+ * 認証付きCRUDルートを、ページ側のuseAuthと同じAuthProviderで囲むレイアウトを生成する。
20
+ */
21
+ export function generateAuthLayout(model: ModelInfo, authOptions?: UIAuthOptions): string | undefined {
22
+ if (!authOptions) return undefined;
23
+
24
+ const authContextImport = authOptions.authContextImport ?? '@/lib/auth/auth-context';
25
+
26
+ return `import { AuthProvider } from '${authContextImport}';
27
+ import type { ReactNode } from 'react';
28
+
29
+ export default function ${model.name}Layout({
30
+ children,
31
+ }: {
32
+ children: ReactNode;
33
+ }) {
34
+ return <AuthProvider>{children}</AuthProvider>;
35
+ }
36
+ `;
37
+ }
7
38
 
8
39
  /**
9
40
  * 一覧画面を生成
10
41
  */
11
- export function generateListPage(model: ModelInfo, sharedPackageName: string): string {
42
+ export function generateListPage(model: ModelInfo, sharedPackageName: string, authOptions?: UIAuthOptions): string {
12
43
  const modelName = model.name;
13
44
  const modelCamel = toCamelCase(modelName);
14
45
  const modelKebab = toKebabCase(modelName);
15
46
 
47
+ const hasAuth = !!authOptions;
48
+ const authContextImport = authOptions?.authContextImport ?? '@/lib/auth/auth-context';
49
+ const writeRoles = authOptions?.writeRoles ?? (Array.isArray(authOptions?.authPolicy?.write) ? authOptions.authPolicy.write : authOptions?.authPolicy?.roles);
50
+ const hasWriteRoles = hasAuth && writeRoles && writeRoles.length > 0;
51
+
16
52
  // フィールドから表示するカラムを抽出(id以外の最初の3つ)
17
53
  const displayFields = model.fields
18
54
  .filter(f => f.name !== 'id')
@@ -22,10 +58,6 @@ export function generateListPage(model: ModelInfo, sharedPackageName: string): s
22
58
  const foreignKeyFields = displayFields.filter(f => f.isForeignKey);
23
59
  const hasForeignKeys = foreignKeyFields.length > 0;
24
60
 
25
- // ネストスキーマフィールドを検出
26
- const nestedFields = displayFields.filter(f => f.isNestedSchema);
27
- const hasNestedSchemas = nestedFields.length > 0;
28
-
29
61
  // 外部キー用のstate定義を生成
30
62
  const foreignKeyStates = foreignKeyFields.map(f => {
31
63
  const refModel = f.referencedModel!;
@@ -39,7 +71,7 @@ export function generateListPage(model: ModelInfo, sharedPackageName: string): s
39
71
  const refModelCamel = toCamelCase(refModel);
40
72
  return ` fetch('/api/${refModelCamel}')
41
73
  .then(res => res.json())
42
- .then((data: any[]) => {
74
+ .then((data: Array<{ id: string; name?: string; title?: string }>) => {
43
75
  const map: Record<string, string> = {};
44
76
  data.forEach(item => {
45
77
  // name または title フィールドを表示用文字列として使用
@@ -64,6 +96,7 @@ import { useEffect, useState } from 'react';
64
96
  import Link from 'next/link';
65
97
  import { z } from 'zod/v4';
66
98
  ${schemaImportLine}
99
+ ${hasAuth ? `import { useAuth } from '${authContextImport}';` : ''}
67
100
 
68
101
  type ${modelName} = z.infer<typeof ${localSchemaName}>;
69
102
 
@@ -72,6 +105,9 @@ export default function ${modelName}ListPage() {
72
105
  const [loading, setLoading] = useState(true);
73
106
  const [error, setError] = useState<string | null>(null);
74
107
  ${hasForeignKeys ? foreignKeyStates : ''}
108
+ ${hasWriteRoles ? ` const { hasAnyRole } = useAuth();
109
+ const canWrite = hasAnyRole(${JSON.stringify(writeRoles)});` : hasAuth ? ` const { user } = useAuth();
110
+ const canWrite = !!user;` : ''}
75
111
 
76
112
  useEffect(() => {
77
113
  fetch('/api/${modelCamel}')
@@ -102,8 +138,8 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
102
138
  if (!res.ok) throw new Error('Failed to delete ${modelCamel}');
103
139
 
104
140
  set${modelName}s(${modelCamel}s.filter((item) => item.id !== id));
105
- } catch (err: any) {
106
- alert(\`Error: \${err.message}\`);
141
+ } catch (err) {
142
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
107
143
  }
108
144
  };
109
145
 
@@ -135,12 +171,19 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
135
171
  </div>
136
172
  <div className="flex justify-between items-center mb-6">
137
173
  <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">${modelName}</h1>
138
- <Link
174
+ ${hasAuth ? ` {canWrite && (
175
+ <Link
176
+ href="/${modelKebab}/new"
177
+ className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded"
178
+ >
179
+ Create New
180
+ </Link>
181
+ )}` : ` <Link
139
182
  href="/${modelKebab}/new"
140
183
  className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded"
141
184
  >
142
185
  Create New
143
- </Link>
186
+ </Link>`}
144
187
  </div>
145
188
 
146
189
  {${modelCamel}s.length === 0 ? (
@@ -173,7 +216,7 @@ ${displayFields.map(f => {
173
216
  const displayField = f.nestedDisplayField || 'name';
174
217
  if (f.isArray) {
175
218
  return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
176
- {Array.isArray(item.${f.name}) ? item.${f.name}.map((ref: any) => ref?.${displayField} || '-').join(', ') : '-'}
219
+ {Array.isArray(item.${f.name}) ? item.${f.name}.map((ref: unknown) => (ref as { ${displayField}?: string } | null)?.${displayField} || '-').join(', ') : '-'}
177
220
  </td>`;
178
221
  } else {
179
222
  return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
@@ -183,6 +226,11 @@ ${displayFields.map(f => {
183
226
  } else if (f.isForeignKey && f.referencedModel) {
184
227
  const refModel = f.referencedModel;
185
228
  const refModelCamel = toCamelCase(refModel);
229
+ if (f.isOptional || f.isNullable) {
230
+ return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
231
+ {item.${f.name} ? ${refModelCamel}Map[item.${f.name}] || item.${f.name} : '-'}
232
+ </td>`;
233
+ }
186
234
  return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
187
235
  {${refModelCamel}Map[item.${f.name}] || item.${f.name}}
188
236
  </td>`;
@@ -199,7 +247,22 @@ ${displayFields.map(f => {
199
247
  >
200
248
  View
201
249
  </Link>
202
- <Link
250
+ ${hasAuth ? ` {canWrite && (
251
+ <>
252
+ <Link
253
+ href={\`/${modelKebab}/\${item.id}/edit\`}
254
+ className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 mr-4"
255
+ >
256
+ Edit
257
+ </Link>
258
+ <button
259
+ onClick={() => handleDelete(item.id)}
260
+ className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
261
+ >
262
+ Delete
263
+ </button>
264
+ </>
265
+ )}` : ` <Link
203
266
  href={\`/${modelKebab}/\${item.id}/edit\`}
204
267
  className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 mr-4"
205
268
  >
@@ -210,7 +273,7 @@ ${displayFields.map(f => {
210
273
  className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
211
274
  >
212
275
  Delete
213
- </button>
276
+ </button>`}
214
277
  </td>
215
278
  </tr>
216
279
  ))}
@@ -227,11 +290,16 @@ ${displayFields.map(f => {
227
290
  /**
228
291
  * 詳細画面を生成
229
292
  */
230
- export function generateDetailPage(model: ModelInfo, sharedPackageName: string): string {
293
+ export function generateDetailPage(model: ModelInfo, sharedPackageName: string, authOptions?: UIAuthOptions): string {
231
294
  const modelName = model.name;
232
295
  const modelCamel = toCamelCase(modelName);
233
296
  const modelKebab = toKebabCase(modelName);
234
297
 
298
+ const hasAuth = !!authOptions;
299
+ const authContextImport = authOptions?.authContextImport ?? '@/lib/auth/auth-context';
300
+ const writeRoles = authOptions?.writeRoles ?? (Array.isArray(authOptions?.authPolicy?.write) ? authOptions.authPolicy.write : authOptions?.authPolicy?.roles);
301
+ const hasWriteRoles = hasAuth && writeRoles && writeRoles.length > 0;
302
+
235
303
  // 外部キーフィールドを検出
236
304
  const foreignKeyFields = model.fields.filter(f => f.isForeignKey);
237
305
  const hasForeignKeys = foreignKeyFields.length > 0;
@@ -249,7 +317,7 @@ export function generateDetailPage(model: ModelInfo, sharedPackageName: string):
249
317
  const refModelCamel = toCamelCase(refModel);
250
318
  return ` fetch('/api/${refModelCamel}')
251
319
  .then(res => res.json())
252
- .then((data: any[]) => {
320
+ .then((data: Array<{ id: string; name?: string; title?: string }>) => {
253
321
  const map: Record<string, string> = {};
254
322
  data.forEach(item => {
255
323
  map[item.id] = item.name || item.title || item.id;
@@ -274,6 +342,7 @@ import { useParams, useRouter } from 'next/navigation';
274
342
  import Link from 'next/link';
275
343
  import { z } from 'zod/v4';
276
344
  ${schemaImportLine}
345
+ ${hasAuth ? `import { useAuth } from '${authContextImport}';` : ''}
277
346
 
278
347
  type ${modelName} = z.infer<typeof ${localSchemaName}>;
279
348
 
@@ -284,6 +353,9 @@ export default function ${modelName}DetailPage() {
284
353
  const [loading, setLoading] = useState(true);
285
354
  const [error, setError] = useState<string | null>(null);
286
355
  ${hasForeignKeys ? foreignKeyStates : ''}
356
+ ${hasWriteRoles ? ` const { hasAnyRole } = useAuth();
357
+ const canWrite = hasAnyRole(${JSON.stringify(writeRoles)});` : hasAuth ? ` const { user } = useAuth();
358
+ const canWrite = !!user;` : ''}
287
359
 
288
360
  useEffect(() => {
289
361
  const id = params?.id as string;
@@ -317,8 +389,8 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
317
389
  if (!res.ok) throw new Error('Failed to delete ${modelCamel}');
318
390
 
319
391
  router.push('/${modelKebab}');
320
- } catch (err: any) {
321
- alert(\`Error: \${err.message}\`);
392
+ } catch (err) {
393
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
322
394
  }
323
395
  };
324
396
 
@@ -343,7 +415,22 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
343
415
  <div className="max-w-2xl mx-auto">
344
416
  <div className="flex justify-between items-center mb-6">
345
417
  <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">${modelName} Details</h1>
346
- <div className="space-x-2">
418
+ ${hasAuth ? ` {canWrite && (
419
+ <div className="space-x-2">
420
+ <Link
421
+ href={\`/${modelKebab}/\${${modelCamel}.id}/edit\`}
422
+ 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"
423
+ >
424
+ Edit
425
+ </Link>
426
+ <button
427
+ onClick={handleDelete}
428
+ 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"
429
+ >
430
+ Delete
431
+ </button>
432
+ </div>
433
+ )}` : ` <div className="space-x-2">
347
434
  <Link
348
435
  href={\`/${modelKebab}/\${${modelCamel}.id}/edit\`}
349
436
  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"
@@ -356,7 +443,7 @@ ${hasForeignKeys ? foreignKeyFetches : ''}
356
443
  >
357
444
  Delete
358
445
  </button>
359
- </div>
446
+ </div>`}
360
447
  </div>
361
448
 
362
449
  <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
@@ -368,7 +455,7 @@ ${model.fields.map(f => {
368
455
  if (f.isArray) {
369
456
  return ` <div>
370
457
  <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${label}</dt>
371
- <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{Array.isArray(${modelCamel}.${f.name}) ? ${modelCamel}.${f.name}.map((ref: any) => ref?.${displayField} || '-').join(', ') : '-'}</dd>
458
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{Array.isArray(${modelCamel}.${f.name}) ? ${modelCamel}.${f.name}.map((ref: unknown) => (ref as { ${displayField}?: string } | null)?.${displayField} || '-').join(', ') : '-'}</dd>
372
459
  </div>`;
373
460
  } else {
374
461
  return ` <div>
@@ -379,6 +466,12 @@ ${model.fields.map(f => {
379
466
  } else if (f.isForeignKey && f.referencedModel) {
380
467
  const refModel = f.referencedModel;
381
468
  const refModelCamel = toCamelCase(refModel);
469
+ if (f.isOptional || f.isNullable) {
470
+ return ` <div>
471
+ <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${refModel}</dt>
472
+ <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{${modelCamel}.${f.name} ? ${refModelCamel}Map[${modelCamel}.${f.name}] || ${modelCamel}.${f.name} : '-'}</dd>
473
+ </div>`;
474
+ }
382
475
  return ` <div>
383
476
  <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${refModel}</dt>
384
477
  <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{${refModelCamel}Map[${modelCamel}.${f.name}] || ${modelCamel}.${f.name}}</dd>
@@ -454,7 +547,7 @@ export function generateFormComponent(model: ModelInfo, sharedPackageName: strin
454
547
  return ` // ${refModelPascal} の一覧を取得
455
548
  fetch('/api/${refModelCamel}')
456
549
  .then(res => res.json())
457
- .then(data => set${refModelPascal}Options(data.map((item: any) => ({ id: item.id, name: item.${displayField} || item.name || item.title || item.id }))))
550
+ .then(data => set${refModelPascal}Options(data.map((item: Record<string, string>) => ({ id: item.id, name: item.${displayField} || item.name || item.title || item.id }))))
458
551
  .catch(err => console.error('Failed to load ${refModelPascal} options:', err));`;
459
552
  }).join('\n');
460
553
 
@@ -488,7 +581,7 @@ ${formFields.map(f => {
488
581
  // ネストスキーマの場合は参照ID(単一: string, 配列: string[])を管理
489
582
  if (f.isNestedSchema) {
490
583
  if (f.isArray) {
491
- return ` ${f.name}Ids: initialData?.${f.name} ? (Array.isArray(initialData.${f.name}) ? initialData.${f.name}.map((item: any) => item.id) : []) : [] as string[],`;
584
+ return ` ${f.name}Ids: initialData?.${f.name} ? (Array.isArray(initialData.${f.name}) ? initialData.${f.name}.map((item: { id: string }) => item.id) : []) : [] as string[],`;
492
585
  } else {
493
586
  return ` ${f.name}Id: initialData?.${f.name}?.id ?? '',`;
494
587
  }
@@ -522,7 +615,7 @@ ${needsUseEffect ? `
522
615
  ${hasForignKeys ? foreignKeyFields.map(f => ` // ${f.referencedModel} の一覧を取得
523
616
  fetch('/api/${toKebabCase(f.referencedModel!)}')
524
617
  .then(res => res.json())
525
- .then(data => set${f.referencedModel}Options(data.map((item: any) => ({ id: item.id, name: item.name || item.title || item.id }))))
618
+ .then(data => set${f.referencedModel}Options(data.map((item: Record<string, string>) => ({ id: item.id, name: item.name || item.title || item.id }))))
526
619
  .catch(err => console.error('Failed to load ${f.referencedModel} options:', err));`).join('\n') : ''}
527
620
  ${hasNestedSchemas ? nestedSchemaFetches : ''}
528
621
  }, []);
@@ -534,7 +627,7 @@ ${hasNestedSchemas ? nestedSchemaFetches : ''}
534
627
 
535
628
  try {
536
629
  // Array フィールドをカンマ区切りから配列に変換
537
- const submitData: any = { ...formData };
630
+ const submitData: Record<string, unknown> = { ...formData };
538
631
  ${formFields.filter(f => f.isArray && !f.isNestedSchema).map(f => ` if (typeof submitData.${f.name} === 'string') {
539
632
  submitData.${f.name} = submitData.${f.name}.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
540
633
  }`).join('\n')}
@@ -558,7 +651,7 @@ ${nestedSchemaFields.map(f => {
558
651
  if (f.isArray) {
559
652
  return ` // ${refModelPascal} 配列参照の変換
560
653
  if (submitData.${f.name}Ids) {
561
- submitData.${f.name} = submitData.${f.name}Ids
654
+ submitData.${f.name} = (submitData.${f.name}Ids as string[])
562
655
  .map((refId: string) => ${refModelCamel}Options.find(opt => opt.id === refId))
563
656
  .filter(Boolean);
564
657
  delete submitData.${f.name}Ids;
@@ -595,17 +688,17 @@ ${nestedSchemaFields.map(f => {
595
688
  }
596
689
 
597
690
  router.push('/${toKebabCase(modelName)}');
598
- } catch (err: any) {
599
- if (err.issues) {
691
+ } catch (err) {
692
+ if (err instanceof z.ZodError) {
600
693
  // Zod validation errors
601
694
  const fieldErrors: Record<string, string> = {};
602
- err.issues.forEach((error: any) => {
603
- const field = error.path[0];
604
- fieldErrors[field] = error.message;
695
+ err.issues.forEach((issue) => {
696
+ const field = String(issue.path[0]);
697
+ fieldErrors[field] = issue.message;
605
698
  });
606
699
  setErrors(fieldErrors);
607
700
  } else {
608
- alert(\`Error: \${err.message}\`);
701
+ alert(\`Error: \${err instanceof Error ? err.message : String(err)}\`);
609
702
  }
610
703
  setLoading(false);
611
704
  }
@@ -849,11 +942,17 @@ ${f.enumValues.map(v => ` <option value="${v}">${v}</option>`).join('\n
849
942
  /**
850
943
  * 新規作成画面を生成
851
944
  */
852
- export function generateNewPage(model: ModelInfo): string {
945
+ export function generateNewPage(model: ModelInfo, authOptions?: UIAuthOptions): string {
853
946
  const modelName = model.name;
854
947
  const modelKebab = toKebabCase(modelName);
855
948
 
856
- return `import ${modelName}Form from '../_components/${modelName}Form';
949
+ const hasAuth = !!authOptions;
950
+ const authContextImport = authOptions?.authContextImport ?? '@/lib/auth/auth-context';
951
+ const writeRoles = authOptions?.writeRoles ?? (Array.isArray(authOptions?.authPolicy?.write) ? authOptions.authPolicy.write : authOptions?.authPolicy?.roles);
952
+ const hasWriteRoles = hasAuth && writeRoles && writeRoles.length > 0;
953
+
954
+ if (!hasAuth) {
955
+ return `import ${modelName}Form from '../_components/${modelName}Form';
857
956
 
858
957
  export default function New${modelName}Page() {
859
958
  return (
@@ -867,17 +966,61 @@ export default function New${modelName}Page() {
867
966
  </div>
868
967
  );
869
968
  }
969
+ `;
970
+ }
971
+
972
+ return `'use client';
973
+
974
+ import { useEffect } from 'react';
975
+ import { useRouter } from 'next/navigation';
976
+ import ${modelName}Form from '../_components/${modelName}Form';
977
+ import { useAuth } from '${authContextImport}';
978
+
979
+ export default function New${modelName}Page() {
980
+ const router = useRouter();
981
+ ${hasWriteRoles ? ` const { hasAnyRole, loading } = useAuth();
982
+ const canWrite = hasAnyRole(${JSON.stringify(writeRoles)});` : ` const { user, loading } = useAuth();
983
+ const canWrite = !!user;`}
984
+
985
+ useEffect(() => {
986
+ if (!loading && !canWrite) {
987
+ router.push('/${modelKebab}');
988
+ }
989
+ }, [loading, canWrite, router]);
990
+
991
+ if (loading) {
992
+ return <div className="flex items-center justify-center min-h-screen"><div className="text-lg">Loading...</div></div>;
993
+ }
994
+
995
+ if (!canWrite) return null;
996
+
997
+ return (
998
+ <div className="container mx-auto px-4 py-8">
999
+ <div className="max-w-2xl mx-auto">
1000
+ <h1 className="text-3xl font-bold mb-6">Create New ${modelName}</h1>
1001
+ <div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
1002
+ <${modelName}Form />
1003
+ </div>
1004
+ </div>
1005
+ </div>
1006
+ );
1007
+ }
870
1008
  `;
871
1009
  }
872
1010
 
873
1011
  /**
874
1012
  * 編集画面を生成
875
1013
  */
876
- export function generateEditPage(model: ModelInfo, sharedPackageName: string): string {
1014
+ export function generateEditPage(model: ModelInfo, sharedPackageName: string, authOptions?: UIAuthOptions): string {
877
1015
  const modelName = model.name;
878
1016
  const modelCamel = toCamelCase(modelName);
879
1017
  const modelKebab = toKebabCase(modelName);
880
1018
 
1019
+ const hasAuth = !!authOptions;
1020
+ const authContextImport = authOptions?.authContextImport ?? '@/lib/auth/auth-context';
1021
+ const writeRoles = authOptions?.writeRoles ?? (Array.isArray(authOptions?.authPolicy?.write) ? authOptions.authPolicy.write : authOptions?.authPolicy?.roles);
1022
+ const hasWriteRoles = hasAuth && writeRoles && writeRoles.length > 0;
1023
+
881
1024
  const schemaName = model.schemaName;
882
1025
  // Zod公式パターン対応: schemaNameとmodelNameが同じ場合はimportエイリアスで名前衝突を回避
883
1026
  const needsAlias = schemaName === modelName;
@@ -889,17 +1032,29 @@ export function generateEditPage(model: ModelInfo, sharedPackageName: string): s
889
1032
  return `'use client';
890
1033
 
891
1034
  import { useEffect, useState } from 'react';
892
- import { useParams } from 'next/navigation';
1035
+ import { useParams, useRouter } from 'next/navigation';
893
1036
  import ${modelName}Form from '../../_components/${modelName}Form';
894
1037
  import { z } from 'zod/v4';
895
1038
  ${schemaImportLine}
1039
+ ${hasAuth ? `import { useAuth } from '${authContextImport}';` : ''}
896
1040
 
897
1041
  type ${modelName} = z.infer<typeof ${localSchemaName}>;
898
1042
 
899
1043
  export default function Edit${modelName}Page() {
900
1044
  const params = useParams();
1045
+ ${hasAuth ? ' const router = useRouter();' : ''}
901
1046
  const [${modelCamel}, set${modelName}] = useState<${modelName} | null>(null);
902
1047
  const [loading, setLoading] = useState(true);
1048
+ ${hasWriteRoles ? ` const { hasAnyRole, loading: authLoading } = useAuth();
1049
+ const canWrite = hasAnyRole(${JSON.stringify(writeRoles)});` : hasAuth ? ` const { user, loading: authLoading } = useAuth();
1050
+ const canWrite = !!user;` : ''}
1051
+ ${hasAuth ? `
1052
+ useEffect(() => {
1053
+ if (!authLoading && !canWrite) {
1054
+ router.push('/${modelKebab}');
1055
+ }
1056
+ }, [authLoading, canWrite, router]);
1057
+ ` : ''}
903
1058
 
904
1059
  useEffect(() => {
905
1060
  const id = params?.id as string;