swallowkit 1.0.0-beta.11 → 1.0.0-beta.13
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 +33 -9
- package/README.md +33 -9
- package/dist/__tests__/fixtures.d.ts +8 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +60 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- 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 +161 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -0
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +65 -1
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +49 -3
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +130 -7
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +3 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +37 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +67 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +285 -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/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 +1009 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/model-parser.d.ts +6 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +150 -28
- package/dist/core/scaffold/model-parser.js.map +1 -1
- 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 +133 -0
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/config.test.ts +141 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +252 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/fixtures.ts +60 -0
- package/src/__tests__/scaffold.test.ts +2 -2
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-connector.ts +157 -0
- package/src/cli/commands/create-model.ts +72 -2
- package/src/cli/commands/dev.ts +55 -4
- package/src/cli/commands/scaffold.ts +211 -12
- package/src/cli/index.ts +16 -0
- package/src/core/config.ts +42 -1
- package/src/core/mock/connector-mock-server.ts +307 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/scaffold/connector-functions-generator.ts +1106 -0
- package/src/core/scaffold/model-parser.ts +172 -31
- package/src/core/scaffold/nextjs-generator.ts +154 -0
- package/src/types/index.ts +47 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* コネクタ関連のモデルパーサーおよびBFFジェネレーターのテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parseConnectorConfig } from "../core/scaffold/model-parser";
|
|
6
|
+
import { generateConnectorBFFRoutes } from "../core/scaffold/nextjs-generator";
|
|
7
|
+
import {
|
|
8
|
+
createRdbConnectorModelInfo,
|
|
9
|
+
createApiConnectorModelInfo,
|
|
10
|
+
} from "./fixtures";
|
|
11
|
+
|
|
12
|
+
// ─── parseConnectorConfig ───────────────────────────────────
|
|
13
|
+
|
|
14
|
+
describe("parseConnectorConfig", () => {
|
|
15
|
+
it("parses RDB connector config from model content", () => {
|
|
16
|
+
const content = `
|
|
17
|
+
import { z } from 'zod/v4';
|
|
18
|
+
export const User = z.object({ id: z.string(), name: z.string() });
|
|
19
|
+
export type User = z.infer<typeof User>;
|
|
20
|
+
export const displayName = 'User';
|
|
21
|
+
|
|
22
|
+
export const connectorConfig = {
|
|
23
|
+
connector: 'mysql',
|
|
24
|
+
operations: ['getAll', 'getById'] as const,
|
|
25
|
+
table: 'users',
|
|
26
|
+
idColumn: 'id',
|
|
27
|
+
};
|
|
28
|
+
`;
|
|
29
|
+
const result = parseConnectorConfig(content);
|
|
30
|
+
expect(result).toBeDefined();
|
|
31
|
+
expect(result!.connector).toBe("mysql");
|
|
32
|
+
expect(result!.operations).toEqual(["getAll", "getById"]);
|
|
33
|
+
expect((result as any).table).toBe("users");
|
|
34
|
+
expect((result as any).idColumn).toBe("id");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("parses API connector config from model content", () => {
|
|
38
|
+
const content = `
|
|
39
|
+
export const connectorConfig = {
|
|
40
|
+
connector: 'backlog',
|
|
41
|
+
operations: ['getAll', 'getById', 'create', 'update'] as const,
|
|
42
|
+
endpoints: {
|
|
43
|
+
getAll: 'GET /issues',
|
|
44
|
+
getById: 'GET /issues/{id}',
|
|
45
|
+
create: 'POST /issues',
|
|
46
|
+
update: 'PATCH /issues/{id}',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
`;
|
|
50
|
+
const result = parseConnectorConfig(content);
|
|
51
|
+
expect(result).toBeDefined();
|
|
52
|
+
expect(result!.connector).toBe("backlog");
|
|
53
|
+
expect(result!.operations).toEqual(["getAll", "getById", "create", "update"]);
|
|
54
|
+
expect((result as any).endpoints).toEqual({
|
|
55
|
+
getAll: "GET /issues",
|
|
56
|
+
getById: "GET /issues/{id}",
|
|
57
|
+
create: "POST /issues",
|
|
58
|
+
update: "PATCH /issues/{id}",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns undefined when no connectorConfig export exists", () => {
|
|
63
|
+
const content = `
|
|
64
|
+
export const Todo = z.object({ id: z.string() });
|
|
65
|
+
export type Todo = z.infer<typeof Todo>;
|
|
66
|
+
export const displayName = 'Todo';
|
|
67
|
+
`;
|
|
68
|
+
const result = parseConnectorConfig(content);
|
|
69
|
+
expect(result).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns undefined when connector name is missing", () => {
|
|
73
|
+
const content = `
|
|
74
|
+
export const connectorConfig = {
|
|
75
|
+
operations: ['getAll'],
|
|
76
|
+
};
|
|
77
|
+
`;
|
|
78
|
+
const result = parseConnectorConfig(content);
|
|
79
|
+
expect(result).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("handles single quotes and double quotes", () => {
|
|
83
|
+
const content = `
|
|
84
|
+
export const connectorConfig = {
|
|
85
|
+
connector: "postgres",
|
|
86
|
+
operations: ["getAll", "getById"] as const,
|
|
87
|
+
table: "items",
|
|
88
|
+
};
|
|
89
|
+
`;
|
|
90
|
+
const result = parseConnectorConfig(content);
|
|
91
|
+
expect(result).toBeDefined();
|
|
92
|
+
expect(result!.connector).toBe("postgres");
|
|
93
|
+
expect((result as any).table).toBe("items");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("handles API connector without endpoints", () => {
|
|
97
|
+
const content = `
|
|
98
|
+
export const connectorConfig = {
|
|
99
|
+
connector: 'external-api',
|
|
100
|
+
operations: ['getAll'] as const,
|
|
101
|
+
};
|
|
102
|
+
`;
|
|
103
|
+
const result = parseConnectorConfig(content);
|
|
104
|
+
expect(result).toBeDefined();
|
|
105
|
+
expect(result!.connector).toBe("external-api");
|
|
106
|
+
expect(result!.operations).toEqual(["getAll"]);
|
|
107
|
+
expect((result as any).table).toBeUndefined();
|
|
108
|
+
expect((result as any).endpoints).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ─── generateConnectorBFFRoutes ─────────────────────────────
|
|
113
|
+
|
|
114
|
+
describe("generateConnectorBFFRoutes", () => {
|
|
115
|
+
it("generates read-only BFF routes for RDB connector", () => {
|
|
116
|
+
const model = createRdbConnectorModelInfo();
|
|
117
|
+
const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
|
|
118
|
+
|
|
119
|
+
expect(routes.listRoute).toContain("export async function GET");
|
|
120
|
+
expect(routes.listRoute).toContain("/api/user");
|
|
121
|
+
expect(routes.listRoute).not.toContain("export async function POST");
|
|
122
|
+
|
|
123
|
+
expect(routes.detailRoute).toContain("export async function GET");
|
|
124
|
+
expect(routes.detailRoute).not.toContain("export async function PUT");
|
|
125
|
+
expect(routes.detailRoute).not.toContain("export async function DELETE");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("generates read-write BFF routes for API connector", () => {
|
|
129
|
+
const model = createApiConnectorModelInfo();
|
|
130
|
+
const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById", "create", "update"]);
|
|
131
|
+
|
|
132
|
+
expect(routes.listRoute).toContain("export async function GET");
|
|
133
|
+
expect(routes.listRoute).toContain("export async function POST");
|
|
134
|
+
|
|
135
|
+
expect(routes.detailRoute).toContain("export async function GET");
|
|
136
|
+
expect(routes.detailRoute).toContain("export async function PUT");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("does not generate DELETE route when delete is not in operations", () => {
|
|
140
|
+
const model = createApiConnectorModelInfo();
|
|
141
|
+
const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById", "create", "update"]);
|
|
142
|
+
|
|
143
|
+
expect(routes.detailRoute).not.toContain("export async function DELETE");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("uses correct route paths based on model name", () => {
|
|
147
|
+
const model = createApiConnectorModelInfo();
|
|
148
|
+
const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
|
|
149
|
+
|
|
150
|
+
// BacklogIssue → backlogIssue route
|
|
151
|
+
expect(routes.listRoute).toContain("backlogIssue");
|
|
152
|
+
expect(routes.detailRoute).toContain("backlogIssue");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("imports from shared package", () => {
|
|
156
|
+
const model = createRdbConnectorModelInfo();
|
|
157
|
+
const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
|
|
158
|
+
|
|
159
|
+
expect(routes.listRoute).toContain("@myapp/shared");
|
|
160
|
+
expect(routes.detailRoute).toContain("@myapp/shared");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -25,6 +25,66 @@ export function createBasicModelInfo(overrides?: Partial<ModelInfo>): ModelInfo
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* RDB コネクタ (read-only) 付きの ModelInfo フィクスチャ
|
|
30
|
+
*/
|
|
31
|
+
export function createRdbConnectorModelInfo(overrides?: Partial<ModelInfo>): ModelInfo {
|
|
32
|
+
return createBasicModelInfo({
|
|
33
|
+
name: "User",
|
|
34
|
+
displayName: "User",
|
|
35
|
+
schemaName: "userSchema",
|
|
36
|
+
filePath: "/models/user.ts",
|
|
37
|
+
fields: [
|
|
38
|
+
{ name: "id", type: "string", isOptional: false, isArray: false },
|
|
39
|
+
{ name: "employeeCode", type: "string", isOptional: false, isArray: false },
|
|
40
|
+
{ name: "name", type: "string", isOptional: false, isArray: false },
|
|
41
|
+
{ name: "email", type: "string", isOptional: false, isArray: false },
|
|
42
|
+
{ name: "department", type: "string", isOptional: true, isArray: false },
|
|
43
|
+
{ name: "createdAt", type: "string", isOptional: true, isArray: false },
|
|
44
|
+
{ name: "updatedAt", type: "string", isOptional: true, isArray: false },
|
|
45
|
+
],
|
|
46
|
+
connectorConfig: {
|
|
47
|
+
connector: "mysql",
|
|
48
|
+
operations: ["getAll", "getById"],
|
|
49
|
+
table: "users",
|
|
50
|
+
idColumn: "id",
|
|
51
|
+
},
|
|
52
|
+
...overrides,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* API コネクタ (read-write) 付きの ModelInfo フィクスチャ
|
|
58
|
+
*/
|
|
59
|
+
export function createApiConnectorModelInfo(overrides?: Partial<ModelInfo>): ModelInfo {
|
|
60
|
+
return createBasicModelInfo({
|
|
61
|
+
name: "BacklogIssue",
|
|
62
|
+
displayName: "BacklogIssue",
|
|
63
|
+
schemaName: "backlogIssueSchema",
|
|
64
|
+
filePath: "/models/backlog-issue.ts",
|
|
65
|
+
fields: [
|
|
66
|
+
{ name: "id", type: "string", isOptional: false, isArray: false },
|
|
67
|
+
{ name: "projectId", type: "string", isOptional: false, isArray: false },
|
|
68
|
+
{ name: "issueKey", type: "string", isOptional: false, isArray: false },
|
|
69
|
+
{ name: "summary", type: "string", isOptional: false, isArray: false },
|
|
70
|
+
{ name: "description", type: "string", isOptional: true, isArray: false },
|
|
71
|
+
{ name: "createdAt", type: "string", isOptional: true, isArray: false },
|
|
72
|
+
{ name: "updatedAt", type: "string", isOptional: true, isArray: false },
|
|
73
|
+
],
|
|
74
|
+
connectorConfig: {
|
|
75
|
+
connector: "backlog",
|
|
76
|
+
operations: ["getAll", "getById", "create", "update"],
|
|
77
|
+
endpoints: {
|
|
78
|
+
getAll: "GET /issues",
|
|
79
|
+
getById: "GET /issues/{id}",
|
|
80
|
+
create: "POST /issues",
|
|
81
|
+
update: "PATCH /issues/{id}",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
...overrides,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
28
88
|
/**
|
|
29
89
|
* 外部キーを含む ModelInfo フィクスチャ
|
|
30
90
|
*/
|
|
@@ -2,12 +2,12 @@ import * as path from "path";
|
|
|
2
2
|
import { getCSharpSchemaArtifactPruneTargets, getOpenApiGeneratorArgs } from "../cli/commands/scaffold";
|
|
3
3
|
|
|
4
4
|
describe("getOpenApiGeneratorArgs", () => {
|
|
5
|
-
it("
|
|
5
|
+
it("omits supportingFiles for C# model generation to avoid Polly version conflicts", () => {
|
|
6
6
|
const args = getOpenApiGeneratorArgs("spec.json", "out", "csharp");
|
|
7
7
|
const globalPropertyIndex = args.indexOf("--global-property");
|
|
8
8
|
|
|
9
9
|
expect(globalPropertyIndex).toBeGreaterThanOrEqual(0);
|
|
10
|
-
expect(args[globalPropertyIndex + 1]).toBe("models,
|
|
10
|
+
expect(args[globalPropertyIndex + 1]).toBe("models,apis=false,modelDocs=false,modelTests=false");
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("continues omitting supporting files for Python model generation", () => {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod モックデータジェネレーターのテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { generateMockDocument, generateMockDocuments } from "../core/mock/zod-mock-generator";
|
|
6
|
+
import {
|
|
7
|
+
createBasicModelInfo,
|
|
8
|
+
createRdbConnectorModelInfo,
|
|
9
|
+
createApiConnectorModelInfo,
|
|
10
|
+
createModelInfoWithEnum,
|
|
11
|
+
} from "./fixtures";
|
|
12
|
+
|
|
13
|
+
describe("generateMockDocument", () => {
|
|
14
|
+
it("generates a document with all model fields", () => {
|
|
15
|
+
const model = createBasicModelInfo();
|
|
16
|
+
const doc = generateMockDocument(model, 1);
|
|
17
|
+
|
|
18
|
+
expect(doc).toHaveProperty("id");
|
|
19
|
+
expect(doc).toHaveProperty("title");
|
|
20
|
+
expect(doc).toHaveProperty("description");
|
|
21
|
+
expect(doc).toHaveProperty("completed");
|
|
22
|
+
expect(doc).toHaveProperty("createdAt");
|
|
23
|
+
expect(doc).toHaveProperty("updatedAt");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("generates id using model name and index", () => {
|
|
27
|
+
const model = createBasicModelInfo();
|
|
28
|
+
const doc = generateMockDocument(model, 1);
|
|
29
|
+
expect(doc.id).toBe("todo-001");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("generates different values for different indices", () => {
|
|
33
|
+
const model = createBasicModelInfo();
|
|
34
|
+
const doc1 = generateMockDocument(model, 1);
|
|
35
|
+
const doc2 = generateMockDocument(model, 2);
|
|
36
|
+
expect(doc1.id).not.toBe(doc2.id);
|
|
37
|
+
expect(doc1.title).not.toBe(doc2.title);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("generates boolean fields with alternating values", () => {
|
|
41
|
+
const model = createBasicModelInfo();
|
|
42
|
+
const doc1 = generateMockDocument(model, 1);
|
|
43
|
+
const doc2 = generateMockDocument(model, 2);
|
|
44
|
+
expect(typeof doc1.completed).toBe("boolean");
|
|
45
|
+
expect(doc1.completed).not.toBe(doc2.completed);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("generates ISO date strings for *At fields", () => {
|
|
49
|
+
const model = createBasicModelInfo();
|
|
50
|
+
const doc = generateMockDocument(model, 1);
|
|
51
|
+
expect(typeof doc.createdAt).toBe("string");
|
|
52
|
+
// Should be parseable as ISO date
|
|
53
|
+
expect(new Date(doc.createdAt as string).toISOString()).toBe(doc.createdAt);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("generates string values for title fields", () => {
|
|
57
|
+
const model = createBasicModelInfo();
|
|
58
|
+
const doc = generateMockDocument(model, 1);
|
|
59
|
+
expect(typeof doc.title).toBe("string");
|
|
60
|
+
expect(doc.title).toContain("1");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("handles optional string fields", () => {
|
|
64
|
+
const model = createBasicModelInfo();
|
|
65
|
+
const doc = generateMockDocument(model, 1);
|
|
66
|
+
// description is optional but still gets a value for mock data
|
|
67
|
+
expect(typeof doc.description).toBe("string");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("generates email-like values for email fields", () => {
|
|
71
|
+
const model = createRdbConnectorModelInfo();
|
|
72
|
+
const doc = generateMockDocument(model, 1);
|
|
73
|
+
expect(doc.email).toContain("@example.com");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("generates department values for department fields", () => {
|
|
77
|
+
const model = createRdbConnectorModelInfo();
|
|
78
|
+
const doc = generateMockDocument(model, 1);
|
|
79
|
+
expect(typeof doc.department).toBe("string");
|
|
80
|
+
// Should be one of the department names
|
|
81
|
+
expect(["Engineering", "Sales", "Marketing", "Support", "HR"]).toContain(doc.department);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("cycles enum values across indices", () => {
|
|
85
|
+
const model = createModelInfoWithEnum();
|
|
86
|
+
const doc1 = generateMockDocument(model, 1);
|
|
87
|
+
const doc2 = generateMockDocument(model, 2);
|
|
88
|
+
const doc3 = generateMockDocument(model, 3);
|
|
89
|
+
expect(doc1.status).toBe("open");
|
|
90
|
+
expect(doc2.status).toBe("in_progress");
|
|
91
|
+
expect(doc3.status).toBe("closed");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("generates foreign key references for *Id fields", () => {
|
|
95
|
+
const model = createApiConnectorModelInfo();
|
|
96
|
+
const doc = generateMockDocument(model, 1);
|
|
97
|
+
expect(typeof doc.projectId).toBe("string");
|
|
98
|
+
expect(doc.projectId).toContain("project-");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("generateMockDocuments", () => {
|
|
103
|
+
it("generates the specified number of documents", () => {
|
|
104
|
+
const model = createBasicModelInfo();
|
|
105
|
+
const docs = generateMockDocuments(model, 3);
|
|
106
|
+
expect(docs).toHaveLength(3);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("defaults to 5 documents", () => {
|
|
110
|
+
const model = createBasicModelInfo();
|
|
111
|
+
const docs = generateMockDocuments(model);
|
|
112
|
+
expect(docs).toHaveLength(5);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("generates unique ids for each document", () => {
|
|
116
|
+
const model = createBasicModelInfo();
|
|
117
|
+
const docs = generateMockDocuments(model, 5);
|
|
118
|
+
const ids = docs.map((d) => d.id);
|
|
119
|
+
const uniqueIds = new Set(ids);
|
|
120
|
+
expect(uniqueIds.size).toBe(5);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("works with connector models", () => {
|
|
124
|
+
const model = createRdbConnectorModelInfo();
|
|
125
|
+
const docs = generateMockDocuments(model, 3);
|
|
126
|
+
expect(docs).toHaveLength(3);
|
|
127
|
+
// All should have email fields with @example.com
|
|
128
|
+
for (const doc of docs) {
|
|
129
|
+
expect(doc.email).toContain("@example.com");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwallowKit Add-Connector コマンド
|
|
3
|
+
* swallowkit.config.js にコネクタ設定を追加する
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { ensureSwallowKitProject } from "../../core/config";
|
|
9
|
+
import { ConnectorDefinition } from "../../types";
|
|
10
|
+
|
|
11
|
+
interface AddConnectorOptions {
|
|
12
|
+
name: string;
|
|
13
|
+
type: "rdb" | "api";
|
|
14
|
+
provider?: "mysql" | "postgres" | "sqlserver";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* add-connector コマンド
|
|
19
|
+
*/
|
|
20
|
+
export async function addConnectorCommand(options: AddConnectorOptions) {
|
|
21
|
+
ensureSwallowKitProject("add-connector");
|
|
22
|
+
|
|
23
|
+
console.log(`🔌 SwallowKit Add-Connector: Adding '${options.name}' connector...\n`);
|
|
24
|
+
|
|
25
|
+
const configPath = findConfigFile();
|
|
26
|
+
if (!configPath) {
|
|
27
|
+
console.error("❌ swallowkit.config.js not found. Run 'swallowkit init' first.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Build connector definition
|
|
32
|
+
const connectorDef = buildConnectorDefinition(options);
|
|
33
|
+
|
|
34
|
+
// Update config file
|
|
35
|
+
updateConfigWithConnector(configPath, options.name, connectorDef, options);
|
|
36
|
+
|
|
37
|
+
console.log(`\n✅ Connector '${options.name}' added successfully!`);
|
|
38
|
+
console.log("\n📝 Next steps:");
|
|
39
|
+
console.log(` 1. Review the connector settings in ${configPath}`);
|
|
40
|
+
console.log(` 2. Set the required environment variables in functions/local.settings.json`);
|
|
41
|
+
console.log(` 3. Create models with: npx swallowkit create-model <name> --connector=${options.name}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findConfigFile(): string | null {
|
|
45
|
+
const candidates = ["swallowkit.config.js", "swallowkit.config.json", ".swallowkitrc.json"];
|
|
46
|
+
for (const candidate of candidates) {
|
|
47
|
+
const fullPath = path.resolve(process.cwd(), candidate);
|
|
48
|
+
if (fs.existsSync(fullPath)) {
|
|
49
|
+
return fullPath;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildConnectorDefinition(options: AddConnectorOptions): ConnectorDefinition {
|
|
56
|
+
const name = options.name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
57
|
+
|
|
58
|
+
if (options.type === "rdb") {
|
|
59
|
+
return {
|
|
60
|
+
type: "rdb",
|
|
61
|
+
provider: options.provider || "mysql",
|
|
62
|
+
connectionEnvVar: `${name}_CONNECTION_STRING`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
type: "api",
|
|
68
|
+
baseUrlEnvVar: `${name}_API_BASE_URL`,
|
|
69
|
+
auth: {
|
|
70
|
+
type: "apiKey",
|
|
71
|
+
envVar: `${name}_API_KEY`,
|
|
72
|
+
placement: "query",
|
|
73
|
+
paramName: "apiKey",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* swallowkit.config.js にコネクタ設定を追加
|
|
80
|
+
*/
|
|
81
|
+
export function updateConfigWithConnector(
|
|
82
|
+
configPath: string,
|
|
83
|
+
connectorName: string,
|
|
84
|
+
connectorDef: ConnectorDefinition,
|
|
85
|
+
options: AddConnectorOptions
|
|
86
|
+
): void {
|
|
87
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
88
|
+
|
|
89
|
+
if (configPath.endsWith(".json")) {
|
|
90
|
+
// JSON config
|
|
91
|
+
const config = JSON.parse(content);
|
|
92
|
+
if (!config.connectors) {
|
|
93
|
+
config.connectors = {};
|
|
94
|
+
}
|
|
95
|
+
config.connectors[connectorName] = connectorDef;
|
|
96
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
97
|
+
console.log(`✅ Updated: ${configPath}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// JS config — append connectors section
|
|
102
|
+
if (content.includes("connectors:") || content.includes("connectors :")) {
|
|
103
|
+
console.log(`⚠️ 'connectors' section already exists in ${configPath}. Please add the connector manually:`);
|
|
104
|
+
console.log(formatConnectorSnippet(connectorName, connectorDef, options));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find the last property before the closing of module.exports
|
|
109
|
+
const closingBraceIdx = content.lastIndexOf("}");
|
|
110
|
+
if (closingBraceIdx === -1) {
|
|
111
|
+
console.error("❌ Could not parse config file structure. Please add the connector manually:");
|
|
112
|
+
console.log(formatConnectorSnippet(connectorName, connectorDef, options));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check if we need a comma before inserting
|
|
117
|
+
const beforeClosing = content.substring(0, closingBraceIdx).trimEnd();
|
|
118
|
+
const needsComma = !beforeClosing.endsWith(",") && !beforeClosing.endsWith("{");
|
|
119
|
+
|
|
120
|
+
const connectorBlock = generateConnectorJSBlock(connectorName, connectorDef, options);
|
|
121
|
+
const insertion = `${needsComma ? "," : ""}\n // コネクタ定義\n connectors: {\n${connectorBlock}\n },\n`;
|
|
122
|
+
|
|
123
|
+
const newContent = content.substring(0, closingBraceIdx) + insertion + content.substring(closingBraceIdx);
|
|
124
|
+
fs.writeFileSync(configPath, newContent, "utf-8");
|
|
125
|
+
console.log(`✅ Updated: ${configPath}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function generateConnectorJSBlock(name: string, def: ConnectorDefinition, options: AddConnectorOptions): string {
|
|
129
|
+
if (def.type === "rdb") {
|
|
130
|
+
return ` ${name}: {
|
|
131
|
+
type: 'rdb',
|
|
132
|
+
provider: '${(def as any).provider}',
|
|
133
|
+
connectionEnvVar: '${def.connectionEnvVar}',
|
|
134
|
+
},`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const apiDef = def as any;
|
|
138
|
+
let authBlock = "";
|
|
139
|
+
if (apiDef.auth) {
|
|
140
|
+
authBlock = `
|
|
141
|
+
auth: {
|
|
142
|
+
type: '${apiDef.auth.type}',
|
|
143
|
+
envVar: '${apiDef.auth.envVar}',
|
|
144
|
+
placement: '${apiDef.auth.placement || "query"}',
|
|
145
|
+
paramName: '${apiDef.auth.paramName || "apiKey"}',
|
|
146
|
+
},`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return ` ${name}: {
|
|
150
|
+
type: 'api',
|
|
151
|
+
baseUrlEnvVar: '${apiDef.baseUrlEnvVar}',${authBlock}
|
|
152
|
+
},`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatConnectorSnippet(name: string, def: ConnectorDefinition, options: AddConnectorOptions): string {
|
|
156
|
+
return `\n ${name}: ${JSON.stringify(def, null, 4)}\n`;
|
|
157
|
+
}
|
|
@@ -7,12 +7,13 @@ import * as fs from "fs";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import * as readline from "readline";
|
|
9
9
|
import { toPascalCase } from "../../core/scaffold/model-parser";
|
|
10
|
-
import { ensureSwallowKitProject } from "../../core/config";
|
|
10
|
+
import { ensureSwallowKitProject, loadConfig } from "../../core/config";
|
|
11
11
|
import { detectFromProject, getCommands } from "../../utils/package-manager";
|
|
12
12
|
|
|
13
13
|
interface CreateModelOptions {
|
|
14
14
|
names: string[]; // モデル名のリスト(例: ["todo", "user", "post"])
|
|
15
15
|
modelsDir?: string; // モデルディレクトリ(デフォルト: "shared/models")
|
|
16
|
+
connector?: string; // コネクタ名(例: "mysql", "backlog")
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -38,6 +39,58 @@ export const displayName = '${pascalName}';
|
|
|
38
39
|
`;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* コネクタ付きモデルテンプレートを生成
|
|
44
|
+
*/
|
|
45
|
+
function generateConnectorModelTemplate(modelName: string, connectorName: string, connectorType: 'rdb' | 'api'): string {
|
|
46
|
+
const pascalName = toPascalCase(modelName);
|
|
47
|
+
const kebabName = modelName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
48
|
+
const pluralName = kebabName + 's';
|
|
49
|
+
|
|
50
|
+
const schema = `import { z } from 'zod/v4';
|
|
51
|
+
|
|
52
|
+
// ${pascalName} model (Zod official pattern: same name for value and type)
|
|
53
|
+
export const ${pascalName} = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
createdAt: z.string().optional(),
|
|
57
|
+
updatedAt: z.string().optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type ${pascalName} = z.infer<typeof ${pascalName}>;
|
|
61
|
+
|
|
62
|
+
// Display name for UI
|
|
63
|
+
export const displayName = '${pascalName}';
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
if (connectorType === 'rdb') {
|
|
67
|
+
return schema + `
|
|
68
|
+
// SwallowKit Connector Metadata
|
|
69
|
+
export const connectorConfig = {
|
|
70
|
+
connector: '${connectorName}',
|
|
71
|
+
operations: ['getAll', 'getById'] as const,
|
|
72
|
+
table: '${pluralName}',
|
|
73
|
+
idColumn: 'id',
|
|
74
|
+
};
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// API connector
|
|
79
|
+
return schema + `
|
|
80
|
+
// SwallowKit Connector Metadata
|
|
81
|
+
export const connectorConfig = {
|
|
82
|
+
connector: '${connectorName}',
|
|
83
|
+
operations: ['getAll', 'getById', 'create', 'update'] as const,
|
|
84
|
+
endpoints: {
|
|
85
|
+
getAll: 'GET /${pluralName}',
|
|
86
|
+
getById: 'GET /${pluralName}/{id}',
|
|
87
|
+
create: 'POST /${pluralName}',
|
|
88
|
+
update: 'PATCH /${pluralName}/{id}',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
41
94
|
/**
|
|
42
95
|
* ユーザーに確認を求める
|
|
43
96
|
*/
|
|
@@ -64,6 +117,21 @@ export async function createModelCommand(options: CreateModelOptions) {
|
|
|
64
117
|
|
|
65
118
|
console.log("🏗️ SwallowKit Create-Model: Generating model templates...\n");
|
|
66
119
|
|
|
120
|
+
// コネクタ指定時はコネクタの存在を検証
|
|
121
|
+
let connectorType: 'rdb' | 'api' | undefined;
|
|
122
|
+
if (options.connector) {
|
|
123
|
+
const config = loadConfig();
|
|
124
|
+
const connectorDef = config.connectors?.[options.connector];
|
|
125
|
+
if (!connectorDef) {
|
|
126
|
+
console.error(`❌ Connector '${options.connector}' not found in swallowkit.config.js.`);
|
|
127
|
+
console.error(` Available connectors: ${Object.keys(config.connectors || {}).join(', ') || '(none)'}`);
|
|
128
|
+
console.error(` Run 'swallowkit add-connector ${options.connector} --type=<rdb|api>' first.`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
connectorType = connectorDef.type;
|
|
132
|
+
console.log(`🔌 Using connector: ${options.connector} (${connectorType})\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
67
135
|
const modelsDir = options.modelsDir || "shared/models";
|
|
68
136
|
|
|
69
137
|
// shared/models ディレクトリが存在しなければ作成
|
|
@@ -94,7 +162,9 @@ export async function createModelCommand(options: CreateModelOptions) {
|
|
|
94
162
|
}
|
|
95
163
|
|
|
96
164
|
// モデルファイルを生成
|
|
97
|
-
const content =
|
|
165
|
+
const content = options.connector && connectorType
|
|
166
|
+
? generateConnectorModelTemplate(name, options.connector, connectorType)
|
|
167
|
+
: generateModelTemplate(name);
|
|
98
168
|
fs.writeFileSync(filePath, content);
|
|
99
169
|
console.log(`✅ Created: ${filePath}`);
|
|
100
170
|
created.push(kebabName);
|