swallowkit 1.0.0-beta.3 → 1.0.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +353 -215
  3. package/README.md +406 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +57 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +470 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +33 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +628 -146
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +15 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2696 -1706
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +448 -129
  38. package/dist/cli/commands/scaffold.js.map +1 -1
  39. package/dist/cli/index.d.ts +5 -1
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +200 -42
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +8 -2
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +94 -5
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  48. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  49. package/dist/core/mock/connector-mock-server.js +480 -0
  50. package/dist/core/mock/connector-mock-server.js.map +1 -0
  51. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  53. package/dist/core/mock/zod-mock-generator.js +163 -0
  54. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  55. package/dist/core/operations/create-model.d.ts +15 -0
  56. package/dist/core/operations/create-model.d.ts.map +1 -0
  57. package/dist/core/operations/create-model.js +171 -0
  58. package/dist/core/operations/create-model.js.map +1 -0
  59. package/dist/core/operations/runtime.d.ts +32 -0
  60. package/dist/core/operations/runtime.d.ts.map +1 -0
  61. package/dist/core/operations/runtime.js +225 -0
  62. package/dist/core/operations/runtime.js.map +1 -0
  63. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  64. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  65. package/dist/core/operations/scaffold-machine.js +63 -0
  66. package/dist/core/operations/scaffold-machine.js.map +1 -0
  67. package/dist/core/project/manifest.d.ts +92 -0
  68. package/dist/core/project/manifest.d.ts.map +1 -0
  69. package/dist/core/project/manifest.js +321 -0
  70. package/dist/core/project/manifest.js.map +1 -0
  71. package/dist/core/project/validation.d.ts +20 -0
  72. package/dist/core/project/validation.d.ts.map +1 -0
  73. package/dist/core/project/validation.js +209 -0
  74. package/dist/core/project/validation.js.map +1 -0
  75. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  76. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  77. package/dist/core/scaffold/auth-generator.js +1244 -0
  78. package/dist/core/scaffold/auth-generator.js.map +1 -0
  79. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  81. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  82. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  83. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  84. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  85. package/dist/core/scaffold/functions-generator.js +920 -213
  86. package/dist/core/scaffold/functions-generator.js.map +1 -1
  87. package/dist/core/scaffold/model-parser.d.ts +20 -1
  88. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  89. package/dist/core/scaffold/model-parser.js +328 -135
  90. package/dist/core/scaffold/model-parser.js.map +1 -1
  91. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  92. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  93. package/dist/core/scaffold/native-schema-generator.js +677 -0
  94. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  95. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  96. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  97. package/dist/core/scaffold/nextjs-generator.js +314 -182
  98. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  99. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  100. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  101. package/dist/core/scaffold/openapi-generator.js +190 -0
  102. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  103. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  104. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  105. package/dist/core/scaffold/ui-generator.js +768 -663
  106. package/dist/core/scaffold/ui-generator.js.map +1 -1
  107. package/dist/database/base-model.d.ts +3 -3
  108. package/dist/database/base-model.js +3 -3
  109. package/dist/index.d.ts +2 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +2 -1
  112. package/dist/index.js.map +1 -1
  113. package/dist/machine/contracts.d.ts +16 -0
  114. package/dist/machine/contracts.d.ts.map +1 -0
  115. package/dist/machine/contracts.js +3 -0
  116. package/dist/machine/contracts.js.map +1 -0
  117. package/dist/machine/errors.d.ts +11 -0
  118. package/dist/machine/errors.d.ts.map +1 -0
  119. package/dist/machine/errors.js +34 -0
  120. package/dist/machine/errors.js.map +1 -0
  121. package/dist/machine/index.d.ts +3 -0
  122. package/dist/machine/index.d.ts.map +1 -0
  123. package/dist/machine/index.js +156 -0
  124. package/dist/machine/index.js.map +1 -0
  125. package/dist/mcp/index.d.ts +25 -0
  126. package/dist/mcp/index.d.ts.map +1 -0
  127. package/dist/mcp/index.js +184 -0
  128. package/dist/mcp/index.js.map +1 -0
  129. package/dist/types/index.d.ts +65 -0
  130. package/dist/types/index.d.ts.map +1 -1
  131. package/dist/utils/package-manager.d.ts +109 -0
  132. package/dist/utils/package-manager.d.ts.map +1 -0
  133. package/dist/utils/package-manager.js +215 -0
  134. package/dist/utils/package-manager.js.map +1 -0
  135. package/dist/utils/python-uv.d.ts +21 -0
  136. package/dist/utils/python-uv.d.ts.map +1 -0
  137. package/dist/utils/python-uv.js +111 -0
  138. package/dist/utils/python-uv.js.map +1 -0
  139. package/package.json +85 -73
  140. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  141. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  142. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  143. package/src/__tests__/auth.test.ts +654 -0
  144. package/src/__tests__/config.test.ts +274 -0
  145. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  146. package/src/__tests__/connector-mock-server.test.ts +439 -0
  147. package/src/__tests__/connector-model-bff.test.ts +162 -0
  148. package/src/__tests__/dev-seeds.test.ts +173 -0
  149. package/src/__tests__/dev.test.ts +252 -0
  150. package/src/__tests__/fixtures.ts +144 -0
  151. package/src/__tests__/functions-generator.test.ts +237 -0
  152. package/src/__tests__/init.test.ts +115 -0
  153. package/src/__tests__/machine.test.ts +251 -0
  154. package/src/__tests__/mcp.test.ts +117 -0
  155. package/src/__tests__/model-parser.test.ts +52 -0
  156. package/src/__tests__/nextjs-generator.test.ts +97 -0
  157. package/src/__tests__/openapi-generator.test.ts +43 -0
  158. package/src/__tests__/package-manager.test.ts +189 -0
  159. package/src/__tests__/python-uv.test.ts +48 -0
  160. package/src/__tests__/scaffold.test.ts +67 -0
  161. package/src/__tests__/string-utils.test.ts +75 -0
  162. package/src/__tests__/ui-generator.test.ts +144 -0
  163. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  164. package/src/cli/commands/add-auth.ts +500 -0
  165. package/src/cli/commands/add-connector.ts +158 -0
  166. package/src/cli/commands/create-model.ts +62 -0
  167. package/src/cli/commands/dev-seeds.ts +614 -0
  168. package/src/cli/commands/dev.ts +1134 -0
  169. package/src/cli/commands/index.ts +9 -0
  170. package/src/cli/commands/init.ts +3480 -0
  171. package/src/cli/commands/provision.ts +193 -0
  172. package/src/cli/commands/scaffold.ts +1001 -0
  173. package/src/cli/index.ts +196 -0
  174. package/src/core/config.ts +312 -0
  175. package/src/core/mock/connector-mock-server.ts +555 -0
  176. package/src/core/mock/zod-mock-generator.ts +205 -0
  177. package/src/core/operations/create-model.ts +174 -0
  178. package/src/core/operations/runtime.ts +235 -0
  179. package/src/core/operations/scaffold-machine.ts +91 -0
  180. package/src/core/project/manifest.ts +402 -0
  181. package/src/core/project/validation.ts +229 -0
  182. package/src/core/scaffold/auth-generator.ts +1284 -0
  183. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  184. package/src/core/scaffold/functions-generator.ts +970 -0
  185. package/src/core/scaffold/model-parser.ts +841 -0
  186. package/src/core/scaffold/native-schema-generator.ts +798 -0
  187. package/src/core/scaffold/nextjs-generator.ts +370 -0
  188. package/src/core/scaffold/openapi-generator.ts +212 -0
  189. package/src/core/scaffold/ui-generator.ts +1061 -0
  190. package/src/database/base-model.ts +184 -0
  191. package/src/database/client.ts +140 -0
  192. package/src/database/repository.ts +104 -0
  193. package/src/database/runtime-check.ts +25 -0
  194. package/src/index.ts +27 -0
  195. package/src/machine/contracts.ts +17 -0
  196. package/src/machine/errors.ts +34 -0
  197. package/src/machine/index.ts +173 -0
  198. package/src/mcp/index.ts +185 -0
  199. package/src/types/index.ts +134 -0
  200. package/src/utils/package-manager.ts +229 -0
  201. package/src/utils/python-uv.ts +96 -0
@@ -0,0 +1,173 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { createBasicModelInfo } from "./fixtures";
5
+ import {
6
+ buildDefaultCosmosDatabaseName,
7
+ buildSeedTemplateDocument,
8
+ getContainerNameForModel,
9
+ loadDevSeedFiles,
10
+ normalizeSeedIdentifier,
11
+ parseCosmosConnectionString,
12
+ parseSeedDocuments,
13
+ prepareSeedDocumentsForExport,
14
+ resolveLocalCosmosConnectionInfo,
15
+ } from "../cli/commands/dev-seeds";
16
+
17
+ describe("dev seed helpers", () => {
18
+ const originalCwd = process.cwd();
19
+ let tempDir: string;
20
+
21
+ beforeEach(() => {
22
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-dev-seeds-"));
23
+ process.chdir(tempDir);
24
+ });
25
+
26
+ afterEach(() => {
27
+ process.chdir(originalCwd);
28
+ fs.rmSync(tempDir, { recursive: true, force: true });
29
+ });
30
+
31
+ it("builds Cosmos container names from model names", () => {
32
+ expect(getContainerNameForModel(createBasicModelInfo())).toBe("Todos");
33
+ });
34
+
35
+ it("normalizes seed identifiers across common naming styles", () => {
36
+ expect(normalizeSeedIdentifier("todo-items")).toBe("todoitems");
37
+ expect(normalizeSeedIdentifier("Todo_Items")).toBe("todoitems");
38
+ expect(normalizeSeedIdentifier("todoSchema")).toBe("todoschema");
39
+ });
40
+
41
+ it("wraps a single JSON object into a document array", () => {
42
+ expect(parseSeedDocuments('{"id":"todo-001","title":"Hello"}', "todo.json")).toEqual([
43
+ { id: "todo-001", title: "Hello" },
44
+ ]);
45
+ });
46
+
47
+ it("builds default Cosmos database names from package names", () => {
48
+ expect(buildDefaultCosmosDatabaseName("swallowkit")).toBe("SwallowkitDatabase");
49
+ });
50
+
51
+ it("parses Cosmos DB connection strings", () => {
52
+ expect(parseCosmosConnectionString("AccountEndpoint=https://localhost:8081/;AccountKey=test-key==;")).toEqual({
53
+ endpoint: "https://localhost:8081/",
54
+ key: "test-key==",
55
+ });
56
+ });
57
+
58
+ it("loads matching seed files for an environment", async () => {
59
+ const seedsDir = path.join(tempDir, "dev-seeds", "local");
60
+ fs.mkdirSync(seedsDir, { recursive: true });
61
+ fs.writeFileSync(
62
+ path.join(seedsDir, "todo.json"),
63
+ JSON.stringify([{ id: "todo-001", title: "First task" }], null, 2),
64
+ "utf-8"
65
+ );
66
+ fs.writeFileSync(
67
+ path.join(seedsDir, "ignored.json"),
68
+ JSON.stringify([{ id: "ignored-001" }], null, 2),
69
+ "utf-8"
70
+ );
71
+
72
+ const loaded = await loadDevSeedFiles("local", [createBasicModelInfo()]);
73
+
74
+ expect(loaded).toHaveLength(1);
75
+ expect(loaded[0]).toMatchObject({
76
+ containerName: "Todos",
77
+ documents: [{ id: "todo-001", title: "First task" }],
78
+ });
79
+ });
80
+
81
+ it("rejects seed documents without ids", async () => {
82
+ const seedsDir = path.join(tempDir, "dev-seeds", "local");
83
+ fs.mkdirSync(seedsDir, { recursive: true });
84
+ fs.writeFileSync(path.join(seedsDir, "todo.json"), JSON.stringify([{ title: "Missing id" }], null, 2), "utf-8");
85
+
86
+ await expect(loadDevSeedFiles("local", [createBasicModelInfo()])).rejects.toThrow(/non-empty string id/);
87
+ });
88
+
89
+ it("reads local Cosmos DB settings from local.settings.json", () => {
90
+ const functionsDir = path.join(tempDir, "functions");
91
+ fs.mkdirSync(functionsDir, { recursive: true });
92
+ fs.writeFileSync(
93
+ path.join(functionsDir, "local.settings.json"),
94
+ JSON.stringify(
95
+ {
96
+ Values: {
97
+ CosmosDBConnection: "AccountEndpoint=https://localhost:8081/;AccountKey=test-key==;",
98
+ COSMOS_DB_DATABASE_NAME: "CustomDatabase",
99
+ },
100
+ },
101
+ null,
102
+ 2
103
+ ),
104
+ "utf-8"
105
+ );
106
+
107
+ const result = resolveLocalCosmosConnectionInfo("FallbackDatabase", functionsDir);
108
+
109
+ expect(result).toEqual({
110
+ ok: true,
111
+ value: {
112
+ endpoint: "https://localhost:8081/",
113
+ key: "test-key==",
114
+ databaseName: "CustomDatabase",
115
+ localSettingsPath: path.join(functionsDir, "local.settings.json"),
116
+ },
117
+ });
118
+ });
119
+
120
+ it("prepares exported seed documents by removing Cosmos metadata and sorting ids", () => {
121
+ expect(
122
+ prepareSeedDocumentsForExport(
123
+ [
124
+ { id: "todo-002", title: "Second", _etag: "etag-2", _ts: 2 },
125
+ { id: "todo-001", title: "First", _rid: "rid-1", _self: "self-1" },
126
+ ],
127
+ "todo.json"
128
+ )
129
+ ).toEqual([
130
+ { id: "todo-001", title: "First" },
131
+ { id: "todo-002", title: "Second" },
132
+ ]);
133
+ });
134
+
135
+ it("builds nested template documents from related schemas", () => {
136
+ const category = createBasicModelInfo({
137
+ name: "Category",
138
+ displayName: "Category",
139
+ schemaName: "categorySchema",
140
+ filePath: "/models/category.ts",
141
+ fields: [
142
+ { name: "id", type: "string", isOptional: false, isArray: false },
143
+ { name: "name", type: "string", isOptional: false, isArray: false },
144
+ ],
145
+ hasCreatedAt: false,
146
+ hasUpdatedAt: false,
147
+ });
148
+ const todo = createBasicModelInfo({
149
+ fields: [
150
+ { name: "id", type: "string", isOptional: false, isArray: false },
151
+ { name: "title", type: "string", isOptional: false, isArray: false },
152
+ {
153
+ name: "category",
154
+ type: "object",
155
+ isOptional: true,
156
+ isArray: false,
157
+ isNestedSchema: true,
158
+ nestedModelName: "Category",
159
+ nestedSchemaName: "categorySchema",
160
+ },
161
+ ],
162
+ });
163
+
164
+ expect(buildSeedTemplateDocument(todo, [todo, category])).toEqual({
165
+ id: "todo-001",
166
+ title: "todo-title-sample",
167
+ category: {
168
+ id: "category-001",
169
+ name: "category-name-sample",
170
+ },
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,252 @@
1
+ import * as http from "http";
2
+ import { readFileSync } from "fs";
3
+ import * as path from "path";
4
+ import {
5
+ buildFunctionsBaseUrl,
6
+ buildFunctionsStartArgs,
7
+ buildFunctionsCoreToolsCommand,
8
+ buildNextDevArgs,
9
+ buildDevCommand,
10
+ buildPythonFunctionsEnv,
11
+ compareVersionNumbers,
12
+ DevOptions,
13
+ getFunctionsReadinessTimeoutMs,
14
+ getCSharpFunctionsBuildArtifactPaths,
15
+ getPythonVirtualEnvPaths,
16
+ parseCoreToolsVersion,
17
+ waitForHttpServerReady,
18
+ } from "../cli/commands/dev";
19
+ import { CLI_VERSION, createProgram, normalizeDevCommandArgv } from "../cli/index";
20
+
21
+ const packageVersion = JSON.parse(
22
+ readFileSync(path.resolve(__dirname, "../../package.json"), "utf8")
23
+ ).version as string;
24
+
25
+ describe("dev command helpers", () => {
26
+ it("keeps the CLI version in sync with package.json", () => {
27
+ expect(CLI_VERSION).toBe(packageVersion);
28
+ });
29
+
30
+ it("passes the requested port to Azure Functions Core Tools", () => {
31
+ expect(buildFunctionsStartArgs("7076")).toEqual(["start", "--port", "7076"]);
32
+ });
33
+
34
+ it("parses the Core Tools version from CLI output", () => {
35
+ expect(parseCoreToolsVersion("4.6.0+ab90faafcab539d63cd3d0ce5faf1bca4395fccc")).toBe("4.6.0");
36
+ });
37
+
38
+ it("compares Core Tools versions numerically", () => {
39
+ expect(compareVersionNumbers("4.0.5198", "4.6.0")).toBeLessThan(0);
40
+ expect(compareVersionNumbers("4.6.0", "4.6.0")).toBe(0);
41
+ expect(compareVersionNumbers("4.10.0", "4.6.0")).toBeGreaterThan(0);
42
+ });
43
+
44
+ it("falls back to npm Core Tools for older C# isolated hosts", () => {
45
+ expect(buildFunctionsCoreToolsCommand("csharp", "4.0.5198")).toEqual({
46
+ command: "npm",
47
+ argsPrefix: ["exec", "--yes", "azure-functions-core-tools@4", "--"],
48
+ label: "npm exec azure-functions-core-tools@4 (installed func 4.0.5198 is too old for C# isolated)",
49
+ });
50
+ });
51
+
52
+ it("keeps the installed func command for supported Core Tools versions", () => {
53
+ expect(buildFunctionsCoreToolsCommand("csharp", "4.6.0")).toEqual({
54
+ command: "func",
55
+ argsPrefix: [],
56
+ label: "func 4.6.0",
57
+ });
58
+ });
59
+
60
+ it("uses webpack mode for npm-based Next.js dev", () => {
61
+ expect(buildNextDevArgs("npm", "3012")).toEqual(["next", "dev", "--port", "3012", "--webpack"]);
62
+ });
63
+
64
+ it("uses pnpm exec for pnpm-based Next.js dev", () => {
65
+ expect(buildNextDevArgs("pnpm", "3012")).toEqual(["exec", "next", "dev", "--port", "3012", "--webpack"]);
66
+ });
67
+
68
+ it("builds the Azure Functions base URL from host and port", () => {
69
+ expect(buildFunctionsBaseUrl(undefined, "7071")).toBe("http://localhost:7071");
70
+ expect(buildFunctionsBaseUrl("127.0.0.1", "7072")).toBe("http://127.0.0.1:7072");
71
+ });
72
+
73
+ it("gives C# more startup time before reporting readiness timeout", () => {
74
+ expect(getFunctionsReadinessTimeoutMs("csharp")).toBeGreaterThan(getFunctionsReadinessTimeoutMs("typescript"));
75
+ expect(getFunctionsReadinessTimeoutMs("csharp")).toBeGreaterThan(getFunctionsReadinessTimeoutMs("python"));
76
+ });
77
+
78
+ it("waits until an HTTP server responds before reporting ready", async () => {
79
+ const server = http.createServer((_request, response) => {
80
+ response.writeHead(404);
81
+ response.end("not found");
82
+ });
83
+
84
+ await new Promise<void>((resolve) => {
85
+ server.listen(0, "127.0.0.1", () => resolve());
86
+ });
87
+
88
+ const address = server.address();
89
+ if (!address || typeof address === "string") {
90
+ throw new Error("Failed to determine ephemeral port.");
91
+ }
92
+
93
+ try {
94
+ await expect(
95
+ waitForHttpServerReady(`http://127.0.0.1:${address.port}`, 2_000, 50)
96
+ ).resolves.toBe(true);
97
+ } finally {
98
+ await new Promise<void>((resolve, reject) => {
99
+ server.close((error) => {
100
+ if (error) {
101
+ reject(error);
102
+ return;
103
+ }
104
+ resolve();
105
+ });
106
+ });
107
+ }
108
+ });
109
+
110
+ it("builds Python virtual environment paths under functions/.venv", () => {
111
+ const functionsDir = path.join("C:\\repo", "functions");
112
+ const paths = getPythonVirtualEnvPaths(functionsDir);
113
+
114
+ expect(paths.venvDir).toBe(path.join(functionsDir, ".venv"));
115
+ expect(paths.binDir.endsWith(process.platform === "win32" ? path.join(".venv", "Scripts") : path.join(".venv", "bin"))).toBe(true);
116
+ expect(paths.pythonExecutable.endsWith(process.platform === "win32" ? path.join("Scripts", "python.exe") : path.join("bin", "python"))).toBe(true);
117
+ });
118
+
119
+ it("targets C# bin and obj directories for build cleanup", () => {
120
+ const functionsDir = path.join("C:\\repo", "functions");
121
+
122
+ expect(getCSharpFunctionsBuildArtifactPaths(functionsDir)).toEqual([
123
+ path.join(functionsDir, "bin"),
124
+ path.join(functionsDir, "obj"),
125
+ ]);
126
+ });
127
+
128
+ it("injects Python virtual environment settings for Functions", () => {
129
+ const functionsDir = path.join("C:\\repo", "functions");
130
+ const env = buildPythonFunctionsEnv({ PATH: "C:\\Windows\\System32" }, functionsDir);
131
+ const expectedBinDir = getPythonVirtualEnvPaths(functionsDir).binDir;
132
+
133
+ expect(env.VIRTUAL_ENV).toBe(path.join(functionsDir, ".venv"));
134
+ expect(env.languageWorkers__python__defaultExecutablePath).toBe(
135
+ getPythonVirtualEnvPaths(functionsDir).pythonExecutable
136
+ );
137
+ expect(env.PATH?.startsWith(`${expectedBinDir}${path.delimiter}`)).toBe(true);
138
+ });
139
+ });
140
+
141
+ describe("dev CLI parser", () => {
142
+ async function parseDevOptions(argv: string[]): Promise<DevOptions> {
143
+ let capturedOptions: DevOptions | undefined;
144
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => undefined);
145
+ const program = createProgram(buildDevCommand(async (options) => {
146
+ capturedOptions = options;
147
+ }, () => undefined));
148
+
149
+ program.exitOverride();
150
+
151
+ try {
152
+ await program.parseAsync(normalizeDevCommandArgv(argv));
153
+ } finally {
154
+ logSpy.mockRestore();
155
+ }
156
+
157
+ expect(capturedOptions).toBeDefined();
158
+ return capturedOptions!;
159
+ }
160
+
161
+ it("normalizes dev options placed before the subcommand", () => {
162
+ expect(
163
+ normalizeDevCommandArgv([
164
+ "node",
165
+ "swallowkit",
166
+ "--seed-env",
167
+ "local",
168
+ "--mock-connectors",
169
+ "dev",
170
+ "--port",
171
+ "3001",
172
+ ])
173
+ ).toEqual([
174
+ "node",
175
+ "swallowkit",
176
+ "dev",
177
+ "--seed-env",
178
+ "local",
179
+ "--mock-connectors",
180
+ "--port",
181
+ "3001",
182
+ ]);
183
+ });
184
+
185
+ it("keeps non-dev commands unchanged", () => {
186
+ const argv = ["node", "swallowkit", "create-model", "dev", "--connector", "external"];
187
+ expect(normalizeDevCommandArgv(argv)).toEqual(argv);
188
+ });
189
+
190
+ it("parses the same dev options regardless of whether they appear before or after the subcommand", async () => {
191
+ const expected = {
192
+ port: "3001",
193
+ functionsPort: "7072",
194
+ host: "127.0.0.1",
195
+ open: true,
196
+ verbose: true,
197
+ noFunctions: true,
198
+ seedEnv: "local",
199
+ mockConnectors: true,
200
+ };
201
+
202
+ const optionsAfterCommand = await parseDevOptions([
203
+ "node",
204
+ "swallowkit",
205
+ "dev",
206
+ "--port",
207
+ "3001",
208
+ "--functions-port",
209
+ "7072",
210
+ "--host",
211
+ "127.0.0.1",
212
+ "--open",
213
+ "--verbose",
214
+ "--no-functions",
215
+ "--seed-env",
216
+ "local",
217
+ "--mock-connectors",
218
+ ]);
219
+
220
+ const optionsBeforeCommand = await parseDevOptions([
221
+ "node",
222
+ "swallowkit",
223
+ "--verbose",
224
+ "--host",
225
+ "127.0.0.1",
226
+ "--seed-env",
227
+ "local",
228
+ "--mock-connectors",
229
+ "--no-functions",
230
+ "--port",
231
+ "3001",
232
+ "--functions-port",
233
+ "7072",
234
+ "--open",
235
+ "dev",
236
+ ]);
237
+
238
+ expect(optionsAfterCommand).toMatchObject(expected);
239
+ expect(optionsBeforeCommand).toMatchObject(expected);
240
+ expect(optionsBeforeCommand).toMatchObject(optionsAfterCommand);
241
+ });
242
+
243
+ it("keeps Azure Functions enabled by default", async () => {
244
+ const options = await parseDevOptions([
245
+ "node",
246
+ "swallowkit",
247
+ "dev",
248
+ ]);
249
+
250
+ expect(options.noFunctions).toBe(false);
251
+ });
252
+ });
@@ -0,0 +1,144 @@
1
+ import { ModelInfo } from "../core/scaffold/model-parser";
2
+
3
+ /**
4
+ * テスト用の基本的な ModelInfo フィクスチャ
5
+ */
6
+ export function createBasicModelInfo(overrides?: Partial<ModelInfo>): ModelInfo {
7
+ return {
8
+ name: "Todo",
9
+ displayName: "Todo",
10
+ schemaName: "todoSchema",
11
+ filePath: "/models/todo.ts",
12
+ fields: [
13
+ { name: "id", type: "string", isOptional: false, isArray: false },
14
+ { name: "title", type: "string", isOptional: false, isArray: false },
15
+ { name: "description", type: "string", isOptional: true, isArray: false },
16
+ { name: "completed", type: "boolean", isOptional: false, isArray: false },
17
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
18
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
19
+ ],
20
+ hasId: true,
21
+ hasCreatedAt: true,
22
+ hasUpdatedAt: true,
23
+ nestedSchemaRefs: [],
24
+ partitionKey: '/id',
25
+ ...overrides,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * RDB コネクタ (read-only) 付きの ModelInfo フィクスチャ
31
+ */
32
+ export function createRdbConnectorModelInfo(overrides?: Partial<ModelInfo>): ModelInfo {
33
+ return createBasicModelInfo({
34
+ name: "User",
35
+ displayName: "User",
36
+ schemaName: "userSchema",
37
+ filePath: "/models/user.ts",
38
+ fields: [
39
+ { name: "id", type: "string", isOptional: false, isArray: false },
40
+ { name: "employeeCode", type: "string", isOptional: false, isArray: false },
41
+ { name: "name", type: "string", isOptional: false, isArray: false },
42
+ { name: "email", type: "string", isOptional: false, isArray: false },
43
+ { name: "department", type: "string", isOptional: true, isArray: false },
44
+ { name: "createdAt", type: "string", isOptional: true, isArray: false },
45
+ { name: "updatedAt", type: "string", isOptional: true, isArray: false },
46
+ ],
47
+ connectorConfig: {
48
+ connector: "mysql",
49
+ operations: ["getAll", "getById"],
50
+ table: "users",
51
+ idColumn: "id",
52
+ },
53
+ ...overrides,
54
+ });
55
+ }
56
+
57
+ /**
58
+ * API コネクタ (read-write) 付きの ModelInfo フィクスチャ
59
+ */
60
+ export function createApiConnectorModelInfo(overrides?: Partial<ModelInfo>): ModelInfo {
61
+ return createBasicModelInfo({
62
+ name: "BacklogIssue",
63
+ displayName: "BacklogIssue",
64
+ schemaName: "backlogIssueSchema",
65
+ filePath: "/models/backlog-issue.ts",
66
+ fields: [
67
+ { name: "id", type: "string", isOptional: false, isArray: false },
68
+ { name: "projectId", type: "string", isOptional: false, isArray: false },
69
+ { name: "issueKey", type: "string", isOptional: false, isArray: false },
70
+ { name: "summary", type: "string", isOptional: false, isArray: false },
71
+ { name: "description", type: "string", isOptional: true, isArray: false },
72
+ { name: "createdAt", type: "string", isOptional: true, isArray: false },
73
+ { name: "updatedAt", type: "string", isOptional: true, isArray: false },
74
+ ],
75
+ connectorConfig: {
76
+ connector: "backlog",
77
+ operations: ["getAll", "getById", "create", "update"],
78
+ endpoints: {
79
+ getAll: "GET /issues",
80
+ getById: "GET /issues/{id}",
81
+ create: "POST /issues",
82
+ update: "PATCH /issues/{id}",
83
+ },
84
+ },
85
+ ...overrides,
86
+ });
87
+ }
88
+
89
+ /**
90
+ * 外部キーを含む ModelInfo フィクスチャ
91
+ */
92
+ export function createModelInfoWithForeignKey(): ModelInfo {
93
+ return createBasicModelInfo({
94
+ name: "Task",
95
+ displayName: "Task",
96
+ schemaName: "taskSchema",
97
+ filePath: "/models/task.ts",
98
+ fields: [
99
+ { name: "id", type: "string", isOptional: false, isArray: false },
100
+ { name: "title", type: "string", isOptional: false, isArray: false },
101
+ {
102
+ name: "categoryId",
103
+ type: "string",
104
+ isOptional: false,
105
+ isArray: false,
106
+ isForeignKey: true,
107
+ referencedModel: "Category",
108
+ },
109
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
110
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
111
+ ],
112
+ });
113
+ }
114
+
115
+ /**
116
+ * enum フィールドを含む ModelInfo フィクスチャ
117
+ */
118
+ export function createModelInfoWithEnum(): ModelInfo {
119
+ return createBasicModelInfo({
120
+ name: "Issue",
121
+ displayName: "Issue",
122
+ schemaName: "issueSchema",
123
+ filePath: "/models/issue.ts",
124
+ fields: [
125
+ { name: "id", type: "string", isOptional: false, isArray: false },
126
+ { name: "title", type: "string", isOptional: false, isArray: false },
127
+ {
128
+ name: "status",
129
+ type: "string",
130
+ isOptional: false,
131
+ isArray: false,
132
+ enumValues: ["open", "in_progress", "closed"],
133
+ },
134
+ {
135
+ name: "priority",
136
+ type: "number",
137
+ isOptional: true,
138
+ isArray: false,
139
+ },
140
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
141
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
142
+ ],
143
+ });
144
+ }