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.
- package/README.ja.md +87 -181
- package/README.md +87 -238
- package/dist/__tests__/fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +101 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/add-auth.d.ts +15 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +790 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +164 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +20 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +27 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
- package/dist/cli/commands/dev-seeds.js +188 -6
- package/dist/cli/commands/dev-seeds.js.map +1 -1
- package/dist/cli/commands/dev.d.ts +30 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +495 -129
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +69 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +788 -766
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +24 -3
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +408 -189
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +5 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +335 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +13 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +151 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- package/dist/core/operations/auth-operations.d.ts +61 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +272 -0
- package/dist/core/operations/auth-operations.js.map +1 -0
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +185 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/file-session.d.ts +56 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +203 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +37 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +248 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +51 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +187 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/agent-instructions.d.ts +29 -0
- package/dist/core/project/agent-instructions.d.ts.map +1 -0
- package/dist/core/project/agent-instructions.js +160 -0
- package/dist/core/project/agent-instructions.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/capabilities.d.ts +58 -0
- package/dist/core/project/capabilities.d.ts.map +1 -0
- package/dist/core/project/capabilities.js +135 -0
- package/dist/core/project/capabilities.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +188 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +274 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +299 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +63 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1774 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1029 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +4 -3
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +405 -134
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +229 -78
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +753 -0
- package/dist/core/scaffold/native-schema-generator.js.map +1 -0
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +152 -8
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/ui-generator.d.ts +17 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +174 -34
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +69 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +412 -0
- package/dist/core/verify/index.js.map +1 -0
- package/dist/machine/contracts.d.ts +24 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +14 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +42 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +566 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +425 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +105 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +7 -11
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +42 -17
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +21 -0
- package/dist/utils/python-uv.d.ts.map +1 -0
- package/dist/utils/python-uv.js +111 -0
- package/dist/utils/python-uv.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- package/package.json +15 -17
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
- package/src/__tests__/agent-instructions.test.ts +237 -0
- package/src/__tests__/agent-loop-phase34.test.ts +513 -0
- package/src/__tests__/auth.test.ts +770 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/config.test.ts +176 -3
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +459 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
- package/src/__tests__/dev-seeds.test.ts +124 -0
- package/src/__tests__/dev.test.ts +254 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/fixtures.ts +101 -0
- package/src/__tests__/functions-generator.test.ts +137 -1
- package/src/__tests__/init.test.ts +366 -2
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +371 -0
- package/src/__tests__/mcp.test.ts +253 -0
- package/src/__tests__/model-parser.test.ts +142 -0
- package/src/__tests__/multi-auth.test.ts +160 -0
- package/src/__tests__/nextjs-generator.test.ts +8 -0
- package/src/__tests__/package-manager.test.ts +59 -4
- package/src/__tests__/plan-apply.test.ts +499 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +287 -24
- package/src/__tests__/tsconfig.json +15 -0
- package/src/__tests__/ui-generator.test.ts +104 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +853 -0
- package/src/cli/commands/add-connector.ts +159 -0
- package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
- package/src/cli/commands/create-model.ts +21 -100
- package/src/cli/commands/dev-seeds.ts +278 -7
- package/src/cli/commands/dev.ts +607 -160
- package/src/cli/commands/init.ts +851 -786
- package/src/cli/commands/scaffold.ts +531 -236
- package/src/cli/index.ts +338 -60
- package/src/core/config.ts +145 -9
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/auth-operations.ts +360 -0
- package/src/core/operations/create-model.ts +191 -0
- package/src/core/operations/file-session.ts +222 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +258 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +77 -0
- package/src/core/operations/scaffold-plan.ts +215 -0
- package/src/core/project/agent-instructions.ts +167 -0
- package/src/core/project/agent-templates/AGENTS.md +504 -0
- package/src/core/project/agent-templates/CLAUDE.md +31 -0
- package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
- package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
- package/src/core/project/agent-templates/copilot-instructions.md +27 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/capabilities.ts +193 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/validation.ts +295 -0
- package/src/core/project/workflows.ts +295 -0
- package/src/core/scaffold/auth-generator.ts +1824 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +432 -134
- package/src/core/scaffold/model-parser.ts +244 -90
- package/src/core/scaffold/native-schema-generator.ts +904 -0
- package/src/core/scaffold/nextjs-generator.ts +174 -9
- package/src/core/scaffold/ui-generator.ts +191 -36
- package/src/core/verify/index.ts +468 -0
- package/src/machine/contracts.ts +33 -0
- package/src/machine/errors.ts +45 -0
- package/src/machine/index.ts +690 -0
- package/src/mcp/index.ts +423 -0
- package/src/types/index.ts +137 -0
- package/src/utils/package-manager.ts +41 -19
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
|
@@ -3,16 +3,52 @@
|
|
|
3
3
|
* CRUD 画面(一覧、詳細、新規作成、編集)を生成する
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ModelInfo,
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
599
|
-
if (err.
|
|
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((
|
|
603
|
-
const field =
|
|
604
|
-
fieldErrors[field] =
|
|
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
|
-
|
|
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;
|