swallowkit 1.0.0-beta.9 → 1.0.1
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 +190 -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 +619 -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 +203 -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,8 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as fs from "fs";
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
6
7
|
import * as path from "path";
|
|
7
|
-
import
|
|
8
|
+
import * as ts from "typescript";
|
|
9
|
+
|
|
10
|
+
import { ModelConnectorConfig, ModelAuthPolicy } from "../../types";
|
|
8
11
|
|
|
9
12
|
export interface ModelInfo {
|
|
10
13
|
name: string; // モデル名(例: "Todo")
|
|
@@ -16,12 +19,16 @@ export interface ModelInfo {
|
|
|
16
19
|
hasCreatedAt: boolean; // createdAt フィールドがあるか
|
|
17
20
|
hasUpdatedAt: boolean; // updatedAt フィールドがあるか
|
|
18
21
|
nestedSchemaRefs: NestedSchemaRef[]; // ネストしたスキーマ参照
|
|
22
|
+
connectorConfig?: ModelConnectorConfig; // コネクタメタデータ(外部データソース用)
|
|
23
|
+
authPolicy?: ModelAuthPolicy; // 認可ポリシー(ロールベースアクセス制御用)
|
|
24
|
+
partitionKey: string; // Cosmos DB パーティションキーパス(デフォルト: "/id")
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export interface FieldInfo {
|
|
22
28
|
name: string;
|
|
23
29
|
type: string; // "string" | "number" | "boolean" | "date" | "object" | "array"
|
|
24
30
|
isOptional: boolean;
|
|
31
|
+
isNullable?: boolean;
|
|
25
32
|
isArray: boolean;
|
|
26
33
|
enumValues?: string[]; // enum の場合の選択肢
|
|
27
34
|
isForeignKey?: boolean; // 外部キーかどうか
|
|
@@ -60,11 +67,15 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
60
67
|
const modelName = toPascalCase(fileName);
|
|
61
68
|
|
|
62
69
|
// スキーマ変数名を抽出
|
|
70
|
+
// コメント内のコードパターンを誤認識しないよう、先にコメントを除去してから正規表現を適用する
|
|
63
71
|
// パターン1: export const todoSchema = z.object({ ... }) (camelCase + Schema接尾辞)
|
|
64
72
|
// パターン2: export const Todo = z.object({ ... }) (Zod公式パターン)
|
|
65
|
-
|
|
73
|
+
const contentWithoutComments = content
|
|
74
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // ブロックコメントを除去
|
|
75
|
+
.replace(/\/\/.*/g, ''); // 行コメントを除去
|
|
76
|
+
let schemaMatch = contentWithoutComments.match(/export\s+const\s+(\w+Schema)\s*=/);
|
|
66
77
|
if (!schemaMatch) {
|
|
67
|
-
schemaMatch =
|
|
78
|
+
schemaMatch = contentWithoutComments.match(/export\s+const\s+(\w+)\s*=\s*z\.object\s*\(/);
|
|
68
79
|
}
|
|
69
80
|
if (!schemaMatch) {
|
|
70
81
|
throw new Error(`Could not find exported schema in ${modelPath}. Expected patterns:\n - export const xxxSchema = z.object({ ... })\n - export const Xxx = z.object({ ... })`);
|
|
@@ -75,6 +86,15 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
75
86
|
// displayName を抽出(例: export const displayName = 'Task')
|
|
76
87
|
const displayNameMatch = content.match(/export\s+const\s+displayName\s*=\s*['"]([^'"]+)['"]/);
|
|
77
88
|
const displayName = displayNameMatch ? displayNameMatch[1] : modelName;
|
|
89
|
+
|
|
90
|
+
// connectorConfig を抽出(外部データソース用メタデータ)
|
|
91
|
+
const connectorConfig = parseConnectorConfig(content);
|
|
92
|
+
|
|
93
|
+
// authPolicy を抽出(ロールベースアクセス制御用メタデータ)
|
|
94
|
+
const authPolicy = parseAuthPolicy(content);
|
|
95
|
+
|
|
96
|
+
// partitionKey を抽出(Cosmos DB パーティションキー)
|
|
97
|
+
const partitionKey = parsePartitionKey(content);
|
|
78
98
|
|
|
79
99
|
// ネストしたスキーマ参照を検出
|
|
80
100
|
const nestedSchemaRefs = detectNestedSchemaRefs(modelPath, content, schemaName);
|
|
@@ -89,6 +109,17 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
89
109
|
const hasId = fields.some(f => f.name === "id");
|
|
90
110
|
const hasCreatedAt = fields.some(f => f.name === "createdAt");
|
|
91
111
|
const hasUpdatedAt = fields.some(f => f.name === "updatedAt");
|
|
112
|
+
|
|
113
|
+
// パーティションキーのバリデーション
|
|
114
|
+
if (partitionKey !== '/id') {
|
|
115
|
+
if (!partitionKey.startsWith('/')) {
|
|
116
|
+
console.warn(`⚠️ [${modelName}] partitionKey should start with '/': got '${partitionKey}'`);
|
|
117
|
+
}
|
|
118
|
+
const pkField = partitionKey.startsWith('/') ? partitionKey.slice(1) : partitionKey;
|
|
119
|
+
if (fields.length > 0 && !fields.some(f => f.name === pkField)) {
|
|
120
|
+
console.warn(`⚠️ [${modelName}] partitionKey field '${pkField}' not found in schema fields`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
92
123
|
|
|
93
124
|
return {
|
|
94
125
|
name: modelName,
|
|
@@ -100,6 +131,9 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
100
131
|
hasCreatedAt,
|
|
101
132
|
hasUpdatedAt,
|
|
102
133
|
nestedSchemaRefs,
|
|
134
|
+
partitionKey,
|
|
135
|
+
...(connectorConfig ? { connectorConfig } : {}),
|
|
136
|
+
...(authPolicy ? { authPolicy } : {}),
|
|
103
137
|
};
|
|
104
138
|
}
|
|
105
139
|
|
|
@@ -291,79 +325,46 @@ function mergeNestedSchemaInfo(fields: FieldInfo[], nestedRefs: NestedSchemaRef[
|
|
|
291
325
|
}
|
|
292
326
|
}
|
|
293
327
|
|
|
328
|
+
function compileModelModule(modelPath: string, directory: string, modules = new Map<string, string>()): string {
|
|
329
|
+
const sourcePath = path.resolve(modelPath);
|
|
330
|
+
const existing = modules.get(sourcePath);
|
|
331
|
+
if (existing) return existing;
|
|
332
|
+
const outputPath = path.join(directory, `model-${modules.size}.mjs`);
|
|
333
|
+
modules.set(sourcePath, outputPath);
|
|
334
|
+
const output = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
|
|
335
|
+
fileName: sourcePath,
|
|
336
|
+
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
|
|
337
|
+
transformers: {
|
|
338
|
+
after: [context => source => {
|
|
339
|
+
const visit: ts.Visitor = node => {
|
|
340
|
+
if (ts.isStringLiteral(node) &&
|
|
341
|
+
(ts.isImportDeclaration(node.parent) || ts.isExportDeclaration(node.parent)) &&
|
|
342
|
+
node.text.startsWith(".")) {
|
|
343
|
+
const base = path.resolve(path.dirname(sourcePath), node.text);
|
|
344
|
+
const candidates = [base.replace(/\.js$/, ".ts"), `${base}.ts`, path.join(base, "index.ts"), base];
|
|
345
|
+
const dependency = candidates.find(candidate => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
346
|
+
if (!dependency) throw new Error(`Cannot resolve ${node.text} from ${sourcePath}`);
|
|
347
|
+
const compiled = compileModelModule(dependency, directory, modules);
|
|
348
|
+
return context.factory.createStringLiteral(`./${path.basename(compiled)}`);
|
|
349
|
+
}
|
|
350
|
+
return ts.visitEachChild(node, visit, context);
|
|
351
|
+
};
|
|
352
|
+
return ts.visitNode(source, visit) as ts.SourceFile;
|
|
353
|
+
}],
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
fs.writeFileSync(outputPath, output.outputText);
|
|
357
|
+
return outputPath;
|
|
358
|
+
}
|
|
359
|
+
|
|
294
360
|
/**
|
|
295
361
|
* Zodスキーマから動的にフィールド情報を抽出
|
|
296
362
|
*/
|
|
297
363
|
async function extractFieldsFromSchema(modelPath: string, schemaName: string): Promise<FieldInfo[]> {
|
|
298
364
|
const fields: FieldInfo[] = [];
|
|
365
|
+
let tempDirectory: string | undefined;
|
|
299
366
|
|
|
300
367
|
try {
|
|
301
|
-
// child_processでtsxを実行
|
|
302
|
-
const { execSync } = require('child_process');
|
|
303
|
-
const { tmpdir } = require('os');
|
|
304
|
-
const { join } = require('path');
|
|
305
|
-
const { writeFileSync, unlinkSync, readFileSync } = require('fs');
|
|
306
|
-
|
|
307
|
-
// モデルファイルの内容を読み込む
|
|
308
|
-
let modelContent = readFileSync(modelPath, 'utf8');
|
|
309
|
-
const modelDir = path.dirname(path.resolve(modelPath));
|
|
310
|
-
|
|
311
|
-
// ローカル相対インポートを絶対パスの .mjs import 文に変換して保持
|
|
312
|
-
// パターン: import { categorySchema } from './category'
|
|
313
|
-
const localImports: string[] = [];
|
|
314
|
-
modelContent = modelContent.replace(
|
|
315
|
-
/import\s*\{([^}]+)\}\s*from\s*['"](\.[^'"]+)['"]\s*;?/g,
|
|
316
|
-
(_match: string, imports: string, importPath: string) => {
|
|
317
|
-
// インポートされた変数名を取得
|
|
318
|
-
const importedNames = imports.split(',').map(s => {
|
|
319
|
-
const trimmed = s.trim();
|
|
320
|
-
// 'as' エイリアスに対応
|
|
321
|
-
return trimmed.split(/\s+as\s+/).pop()!.trim();
|
|
322
|
-
}).filter(s => s.length > 0);
|
|
323
|
-
|
|
324
|
-
// 相対パスを絶対パスに解決
|
|
325
|
-
let resolvedPath = path.resolve(modelDir, importPath);
|
|
326
|
-
if (!resolvedPath.endsWith('.ts') && !resolvedPath.endsWith('.js')) {
|
|
327
|
-
resolvedPath += '.ts';
|
|
328
|
-
}
|
|
329
|
-
if (fs.existsSync(resolvedPath)) {
|
|
330
|
-
// インポート先の内容を読み込み、インライン化する
|
|
331
|
-
let refContent = readFileSync(resolvedPath, 'utf8');
|
|
332
|
-
// インポート先の import 文を除去
|
|
333
|
-
refContent = refContent.replace(/import\s+.*?\s+from\s+['"](?!\.).*?['"];?\s*/g, '');
|
|
334
|
-
refContent = refContent.replace(/import\s+.*?\s+from\s+['"]\..*?['"];?\s*/g, '');
|
|
335
|
-
refContent = refContent.replace(/export\s+(const|type|interface|class|function)\s+/g, '$1 ');
|
|
336
|
-
refContent = refContent.replace(/^type\s+\w+\s*=\s*[^;]+;/gm, '');
|
|
337
|
-
refContent = refContent.replace(/^interface\s+\w+\s*\{[\s\S]*?\}/gm, '');
|
|
338
|
-
refContent = refContent.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
339
|
-
refContent = refContent.replace(/\/\/.*/g, '');
|
|
340
|
-
|
|
341
|
-
// インポートされていない const 宣言を除去(displayName 等の重複を防ぐ)
|
|
342
|
-
const importedNameSet = new Set(importedNames);
|
|
343
|
-
refContent = refContent.replace(/^const\s+(\w+)\s*=\s*[^;]+;/gm, (constMatch: string, varName: string) => {
|
|
344
|
-
return importedNameSet.has(varName) ? constMatch : '';
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
localImports.push(refContent.trim());
|
|
348
|
-
}
|
|
349
|
-
return ''; // 元のインポート文は除去
|
|
350
|
-
}
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
// zod 以外のパッケージインポートを除去
|
|
354
|
-
modelContent = modelContent.replace(/import\s+.*?\s+from\s+['"].*?['"];?\s*/g, '');
|
|
355
|
-
modelContent = modelContent.replace(/export\s+(const|type|interface|class|function)\s+/g, '$1 ');
|
|
356
|
-
// type宣言を削除(ランタイムでは不要)
|
|
357
|
-
modelContent = modelContent.replace(/^type\s+\w+\s*=\s*[^;]+;/gm, '');
|
|
358
|
-
// interface宣言を削除(ランタイムでは不要、複数行対応)
|
|
359
|
-
modelContent = modelContent.replace(/^interface\s+\w+\s*\{[\s\S]*?\}/gm, '');
|
|
360
|
-
// コメントを削除
|
|
361
|
-
modelContent = modelContent.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
362
|
-
modelContent = modelContent.replace(/\/\/.*/g, '');
|
|
363
|
-
|
|
364
|
-
// インライン化したローカルインポートを先頭に追加
|
|
365
|
-
const inlinedDeps = localImports.length > 0 ? localImports.join('\n\n') + '\n\n' : '';
|
|
366
|
-
|
|
367
368
|
// プロジェクトルートを探す(package.jsonがある場所)
|
|
368
369
|
let projectRoot = path.dirname(modelPath);
|
|
369
370
|
while (projectRoot !== path.dirname(projectRoot)) {
|
|
@@ -374,16 +375,11 @@ async function extractFieldsFromSchema(modelPath: string, schemaName: string): P
|
|
|
374
375
|
}
|
|
375
376
|
|
|
376
377
|
// プロジェクトルート内に一時スクリプトファイルを作成
|
|
377
|
-
|
|
378
|
+
tempDirectory = fs.mkdtempSync(path.join(projectRoot, '.swallowkit-parser-'));
|
|
379
|
+
const compiledModel = compileModelModule(modelPath, tempDirectory);
|
|
380
|
+
const tempScript = path.join(tempDirectory, 'inspect.mjs');
|
|
378
381
|
const scriptCode = `
|
|
379
|
-
import {
|
|
380
|
-
|
|
381
|
-
// インライン化した依存スキーマ
|
|
382
|
-
${inlinedDeps}
|
|
383
|
-
// モデルファイルの内容を評価
|
|
384
|
-
${modelContent}
|
|
385
|
-
|
|
386
|
-
const schema = ${schemaName};
|
|
382
|
+
import { ${schemaName} as schema } from './${path.basename(compiledModel)}';
|
|
387
383
|
|
|
388
384
|
// Zod v3とv4の両方に対応
|
|
389
385
|
const isObject = (schema && schema._def &&
|
|
@@ -395,10 +391,11 @@ if (isObject) {
|
|
|
395
391
|
const field = shape[key];
|
|
396
392
|
let type = 'string';
|
|
397
393
|
let isOptional = false;
|
|
394
|
+
let isNullable = false;
|
|
398
395
|
let isArray = false;
|
|
399
396
|
let enumValues = undefined;
|
|
400
397
|
|
|
401
|
-
// ZodOptional, ZodDefault, ZodEffects を unwrap
|
|
398
|
+
// ZodOptional, ZodNullable, ZodDefault, ZodEffects を unwrap
|
|
402
399
|
let fieldDef = field;
|
|
403
400
|
const getTypeName = (def) => def?._def?.typeName || def?.constructor?.name || '';
|
|
404
401
|
|
|
@@ -412,6 +409,10 @@ if (isObject) {
|
|
|
412
409
|
isOptional = true;
|
|
413
410
|
fieldDef = fieldDef._def.innerType;
|
|
414
411
|
unwrapped = true;
|
|
412
|
+
} else if (typeName === 'ZodNullable') {
|
|
413
|
+
isNullable = true;
|
|
414
|
+
fieldDef = fieldDef._def.innerType;
|
|
415
|
+
unwrapped = true;
|
|
415
416
|
} else if (typeName === 'ZodDefault') {
|
|
416
417
|
// .default() は optional と同様に扱う
|
|
417
418
|
isOptional = true;
|
|
@@ -427,7 +428,7 @@ if (isObject) {
|
|
|
427
428
|
// ZodArray をチェック
|
|
428
429
|
if (getTypeName(fieldDef) === 'ZodArray') {
|
|
429
430
|
isArray = true;
|
|
430
|
-
fieldDef = fieldDef._def.
|
|
431
|
+
fieldDef = fieldDef._def.element || fieldDef._def.type;
|
|
431
432
|
}
|
|
432
433
|
|
|
433
434
|
// 基本型を判定
|
|
@@ -468,22 +469,17 @@ if (isObject) {
|
|
|
468
469
|
isForeignKey = true;
|
|
469
470
|
}
|
|
470
471
|
|
|
471
|
-
return { name: key, type, isOptional, isArray, enumValues, isForeignKey, referencedModel };
|
|
472
|
+
return { name: key, type, isOptional, isNullable, isArray, enumValues, isForeignKey, referencedModel };
|
|
472
473
|
});
|
|
473
474
|
|
|
474
475
|
console.log(JSON.stringify(fields));
|
|
475
476
|
}
|
|
476
477
|
`;
|
|
477
478
|
|
|
478
|
-
writeFileSync(tempScript, scriptCode, 'utf8');
|
|
479
|
+
fs.writeFileSync(tempScript, scriptCode, 'utf8');
|
|
479
480
|
|
|
480
481
|
try {
|
|
481
|
-
|
|
482
|
-
// Detect package manager from project lockfile
|
|
483
|
-
const { detectFromProject, getCommands } = require('../../utils/package-manager');
|
|
484
|
-
const pm = detectFromProject(projectRoot);
|
|
485
|
-
const pmCmd = getCommands(pm);
|
|
486
|
-
const result = execSync(`${pmCmd.exec} tsx "${tempScript}"`, {
|
|
482
|
+
const result = execFileSync(process.execPath, [tempScript], {
|
|
487
483
|
encoding: 'utf8',
|
|
488
484
|
cwd: projectRoot,
|
|
489
485
|
});
|
|
@@ -497,16 +493,17 @@ if (isObject) {
|
|
|
497
493
|
} finally {
|
|
498
494
|
// 一時ファイルを削除
|
|
499
495
|
try {
|
|
500
|
-
unlinkSync(tempScript);
|
|
496
|
+
fs.unlinkSync(tempScript);
|
|
501
497
|
} catch (e) {
|
|
502
498
|
// ファイル削除失敗は無視
|
|
503
499
|
}
|
|
504
500
|
}
|
|
505
501
|
} catch (error) {
|
|
506
|
-
// tsxが使えない場合は正規表現フォールバック
|
|
507
502
|
console.warn('Failed to use dynamic import, falling back to regex parsing');
|
|
508
503
|
console.warn('Error:', error);
|
|
509
504
|
return extractFieldsWithRegex(modelPath, schemaName);
|
|
505
|
+
} finally {
|
|
506
|
+
if (tempDirectory) fs.rmSync(tempDirectory, { recursive: true, force: true });
|
|
510
507
|
}
|
|
511
508
|
|
|
512
509
|
return fields;
|
|
@@ -545,6 +542,7 @@ function extractFieldsWithRegex(modelPath: string, schemaName: string): FieldInf
|
|
|
545
542
|
name: fieldName,
|
|
546
543
|
type: mapZodTypeToTs(zodType),
|
|
547
544
|
isOptional: fieldDef.includes(".optional()"),
|
|
545
|
+
isNullable: fieldDef.includes(".nullable()"),
|
|
548
546
|
isArray: fieldDef.includes(".array()"),
|
|
549
547
|
});
|
|
550
548
|
}
|
|
@@ -586,6 +584,162 @@ export function toCamelCase(str: string): string {
|
|
|
586
584
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
587
585
|
}
|
|
588
586
|
|
|
587
|
+
/**
|
|
588
|
+
* モデルファイルから connectorConfig エクスポートを静的解析で抽出する
|
|
589
|
+
*/
|
|
590
|
+
export function parseConnectorConfig(content: string): ModelConnectorConfig | undefined {
|
|
591
|
+
// export const connectorConfig = { ... } を検出
|
|
592
|
+
const connectorMatch = content.match(/export\s+const\s+connectorConfig\s*=\s*\{/);
|
|
593
|
+
if (!connectorMatch) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// connectorConfig オブジェクトの内容を抽出
|
|
598
|
+
const startIdx = content.indexOf('{', connectorMatch.index!);
|
|
599
|
+
let braceCount = 1;
|
|
600
|
+
let endIdx = startIdx + 1;
|
|
601
|
+
while (braceCount > 0 && endIdx < content.length) {
|
|
602
|
+
if (content[endIdx] === '{') braceCount++;
|
|
603
|
+
if (content[endIdx] === '}') braceCount--;
|
|
604
|
+
endIdx++;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const objectStr = content.substring(startIdx, endIdx);
|
|
608
|
+
|
|
609
|
+
// connector 名を抽出
|
|
610
|
+
const connectorNameMatch = objectStr.match(/connector\s*:\s*['"]([^'"]+)['"]/);
|
|
611
|
+
if (!connectorNameMatch) {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
const connector = connectorNameMatch[1];
|
|
615
|
+
|
|
616
|
+
// operations を抽出
|
|
617
|
+
const opsMatch = objectStr.match(/operations\s*:\s*\[([^\]]*)\]/);
|
|
618
|
+
const operations: string[] = [];
|
|
619
|
+
if (opsMatch) {
|
|
620
|
+
const opsStr = opsMatch[1];
|
|
621
|
+
const opEntries = opsStr.match(/['"]([^'"]+)['"]/g);
|
|
622
|
+
if (opEntries) {
|
|
623
|
+
for (const entry of opEntries) {
|
|
624
|
+
operations.push(entry.replace(/['"]/g, ''));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// table を抽出(RDB 固有)
|
|
630
|
+
const tableMatch = objectStr.match(/table\s*:\s*['"]([^'"]+)['"]/);
|
|
631
|
+
|
|
632
|
+
// idColumn を抽出(RDB 固有)
|
|
633
|
+
const idColumnMatch = objectStr.match(/idColumn\s*:\s*['"]([^'"]+)['"]/);
|
|
634
|
+
|
|
635
|
+
// endpoints を抽出(API 固有)— endpoint値に {id} 等のプレースホルダが含まれるためbrace-countingで抽出
|
|
636
|
+
let endpoints: Record<string, string> | undefined;
|
|
637
|
+
const endpointsStart = objectStr.match(/endpoints\s*:\s*\{/);
|
|
638
|
+
if (endpointsStart) {
|
|
639
|
+
const epStartIdx = objectStr.indexOf('{', endpointsStart.index!);
|
|
640
|
+
let epBraceCount = 1;
|
|
641
|
+
let epEndIdx = epStartIdx + 1;
|
|
642
|
+
while (epBraceCount > 0 && epEndIdx < objectStr.length) {
|
|
643
|
+
const ch = objectStr[epEndIdx];
|
|
644
|
+
// 文字列内の波括弧をスキップ
|
|
645
|
+
if (ch === "'" || ch === '"') {
|
|
646
|
+
epEndIdx++;
|
|
647
|
+
while (epEndIdx < objectStr.length && objectStr[epEndIdx] !== ch) {
|
|
648
|
+
epEndIdx++;
|
|
649
|
+
}
|
|
650
|
+
} else if (ch === '{') {
|
|
651
|
+
epBraceCount++;
|
|
652
|
+
} else if (ch === '}') {
|
|
653
|
+
epBraceCount--;
|
|
654
|
+
}
|
|
655
|
+
epEndIdx++;
|
|
656
|
+
}
|
|
657
|
+
const epStr = objectStr.substring(epStartIdx + 1, epEndIdx - 1);
|
|
658
|
+
endpoints = {};
|
|
659
|
+
const endpointEntries = epStr.matchAll(/(\w+)\s*:\s*['"]([^'"]+)['"]/g);
|
|
660
|
+
for (const entry of endpointEntries) {
|
|
661
|
+
endpoints[entry[1]] = entry[2];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (tableMatch) {
|
|
666
|
+
// RDB コネクタ
|
|
667
|
+
return {
|
|
668
|
+
connector,
|
|
669
|
+
operations: operations as ModelConnectorConfig['operations'],
|
|
670
|
+
table: tableMatch[1],
|
|
671
|
+
...(idColumnMatch ? { idColumn: idColumnMatch[1] } : {}),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// API コネクタ
|
|
676
|
+
return {
|
|
677
|
+
connector,
|
|
678
|
+
operations: operations as ModelConnectorConfig['operations'],
|
|
679
|
+
...(endpoints ? { endpoints } : {}),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* authPolicy をモデルファイルから抽出
|
|
685
|
+
* パターン: export const authPolicy = { roles: [...], read: [...], write: [...] }
|
|
686
|
+
*/
|
|
687
|
+
export function parseAuthPolicy(content: string): ModelAuthPolicy | undefined {
|
|
688
|
+
const policyMatch = content.match(/export\s+const\s+authPolicy\s*=\s*\{/);
|
|
689
|
+
if (!policyMatch) {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const startIdx = content.indexOf('{', policyMatch.index!);
|
|
694
|
+
let braceCount = 1;
|
|
695
|
+
let endIdx = startIdx + 1;
|
|
696
|
+
while (braceCount > 0 && endIdx < content.length) {
|
|
697
|
+
if (content[endIdx] === '{') braceCount++;
|
|
698
|
+
if (content[endIdx] === '}') braceCount--;
|
|
699
|
+
endIdx++;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const objectStr = content.substring(startIdx, endIdx);
|
|
703
|
+
|
|
704
|
+
const extractRoles = (key: string): string[] | undefined => {
|
|
705
|
+
const match = objectStr.match(new RegExp(`${key}\\s*:\\s*\\[([^\\]]*)\\]`));
|
|
706
|
+
if (!match) return undefined;
|
|
707
|
+
const entries = match[1].match(/['"]([^'"]+)['"]/g);
|
|
708
|
+
if (!entries) return [];
|
|
709
|
+
return entries.map(e => e.replace(/['"]/g, ''));
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const roles = extractRoles('roles');
|
|
713
|
+
const read = extractRoles('read');
|
|
714
|
+
const write = extractRoles('write');
|
|
715
|
+
|
|
716
|
+
const extractName = (key: string): string | undefined => objectStr.match(new RegExp(`${key}\\s*:\\s*['"]([^'"]+)['"]`))?.[1];
|
|
717
|
+
const namedPolicy = extractName('policy');
|
|
718
|
+
const namedRead = extractName('read');
|
|
719
|
+
const namedWrite = extractName('write');
|
|
720
|
+
|
|
721
|
+
if (!roles && !read && !write && !namedPolicy && !namedRead && !namedWrite) {
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
...(roles ? { roles } : {}),
|
|
727
|
+
...(namedRead ? { read: namedRead } : read ? { read } : {}),
|
|
728
|
+
...(namedWrite ? { write: namedWrite } : write ? { write } : {}),
|
|
729
|
+
...(namedPolicy ? { policy: namedPolicy } : {}),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* パーティションキーを抽出する
|
|
735
|
+
* export const partitionKey = '/tenantId' のようなエクスポートを検出
|
|
736
|
+
* 未指定の場合はデフォルト '/id' を返す
|
|
737
|
+
*/
|
|
738
|
+
export function parsePartitionKey(content: string): string {
|
|
739
|
+
const match = content.match(/export\s+const\s+partitionKey\s*=\s*['"]([^'"]+)['"]/);
|
|
740
|
+
return match ? match[1] : '/id';
|
|
741
|
+
}
|
|
742
|
+
|
|
589
743
|
/**
|
|
590
744
|
* 文字列を kebab-case に変換
|
|
591
745
|
*/
|