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,237 @@
1
+ import {
2
+ generateCSharpAzureFunctionsCRUD,
3
+ generateCompactAzureFunctionsCRUD,
4
+ generatePythonAzureFunctionsCRUD,
5
+ } from "../core/scaffold/functions-generator";
6
+ import { createBasicModelInfo, createModelInfoWithEnum } from "./fixtures";
7
+
8
+ describe("generateCompactAzureFunctionsCRUD", () => {
9
+ it("generates correct CRUD code for a basic model", () => {
10
+ const model = createBasicModelInfo();
11
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
12
+ expect(code).toMatchSnapshot();
13
+ });
14
+
15
+ it("generates correct CRUD code for a model with enum fields", () => {
16
+ const model = createModelInfoWithEnum();
17
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
18
+ expect(code).toMatchSnapshot();
19
+ });
20
+
21
+ it("imports the correct schema from shared package", () => {
22
+ const model = createBasicModelInfo();
23
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
24
+ expect(code).toContain("import { todoSchema } from '@myapp/shared'");
25
+ });
26
+
27
+ it("uses correct container name (PascalCase + s)", () => {
28
+ const model = createBasicModelInfo();
29
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
30
+ expect(code).toContain("const containerName = 'Todos'");
31
+ });
32
+
33
+ it("registers correct route names (camelCase)", () => {
34
+ const model = createBasicModelInfo();
35
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
36
+ expect(code).toContain("'todo-get-all'");
37
+ expect(code).toContain("'todo-get-by-id'");
38
+ expect(code).toContain("'todo-create'");
39
+ expect(code).toContain("'todo-update'");
40
+ expect(code).toContain("'todo-delete'");
41
+ });
42
+
43
+ it("generates all CRUD HTTP methods", () => {
44
+ const model = createBasicModelInfo();
45
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
46
+ expect(code).toContain("methods: ['GET']");
47
+ expect(code).toContain("methods: ['POST']");
48
+ expect(code).toContain("methods: ['PUT']");
49
+ expect(code).toContain("methods: ['DELETE']");
50
+ });
51
+
52
+ it("uses correct route patterns", () => {
53
+ const model = createBasicModelInfo();
54
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
55
+ expect(code).toContain("route: 'todo'");
56
+ expect(code).toContain("route: 'todo/{id}'");
57
+ });
58
+
59
+ it("handles multi-word model names correctly", () => {
60
+ const model = createBasicModelInfo({
61
+ name: "TodoItem",
62
+ schemaName: "todoItemSchema",
63
+ });
64
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
65
+ expect(code).toContain("const containerName = 'TodoItems'");
66
+ expect(code).toContain("route: 'todoItem'");
67
+ expect(code).toContain("'todoItem-get-all'");
68
+ });
69
+
70
+ it("generates C# Cosmos-backed CRUD handlers", () => {
71
+ const model = createBasicModelInfo();
72
+ const code = generateCSharpAzureFunctionsCRUD(model);
73
+ expect(code).toContain("public sealed class TodoFunctions");
74
+ expect(code).toContain('[Function("todoGetAll")]');
75
+ expect(code).toContain('Route = "todo/{id}"');
76
+ expect(code).toContain("CreateCosmosClient()");
77
+ expect(code).toContain('new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway }');
78
+ expect(code).toContain('endpoint.Contains("localhost:8081", StringComparison.OrdinalIgnoreCase)');
79
+ expect(code).toContain("container.ReadItemStreamAsync");
80
+ expect(code).toContain("JsonNode.Parse(document.RootElement.GetRawText())?.AsObject()");
81
+ expect(code).toContain("container.CreateItemStreamAsync");
82
+ expect(code).toContain("container.ReplaceItemStreamAsync");
83
+ expect(code).toContain("payload.ToJsonString()");
84
+ expect(code).toContain("container.DeleteItemAsync<JsonObject>");
85
+ });
86
+
87
+ it("generates Python Cosmos-backed CRUD handlers", () => {
88
+ const model = createBasicModelInfo();
89
+ const generated = generatePythonAzureFunctionsCRUD(model);
90
+ expect(generated.registration).toContain("from blueprints.todo import bp as todo_bp");
91
+ expect(generated.registration).toContain("app.register_blueprint(todo_bp)");
92
+ expect(generated.blueprint).toContain('@bp.route(route="todo", methods=["GET"])');
93
+ expect(generated.blueprint).toContain("def todo_create");
94
+ expect(generated.blueprint).toContain("from azure.cosmos import CosmosClient, exceptions");
95
+ expect(generated.blueprint).toContain("container.query_items");
96
+ expect(generated.blueprint).toContain("container.read_item");
97
+ expect(generated.blueprint).toContain("container.create_item");
98
+ expect(generated.blueprint).toContain("container.replace_item");
99
+ expect(generated.blueprint).toContain("container.delete_item");
100
+ });
101
+
102
+ // --- Custom Partition Key Tests ---
103
+
104
+ describe("custom partition key (TS)", () => {
105
+ it("uses input binding when partitionKey is /id (default)", () => {
106
+ const model = createBasicModelInfo({ partitionKey: "/id" });
107
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
108
+ expect(code).toContain("partitionKey: '{id}'");
109
+ expect(code).toContain("container.item(id, id).delete()");
110
+ });
111
+
112
+ it("uses SDK direct call when partitionKey is not /id", () => {
113
+ const model = createBasicModelInfo({
114
+ partitionKey: "/tenantId",
115
+ fields: [
116
+ { name: "id", type: "string", isOptional: false, isArray: false },
117
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
118
+ { name: "title", type: "string", isOptional: false, isArray: false },
119
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
120
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
121
+ ],
122
+ });
123
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
124
+
125
+ // Should NOT have input binding with partitionKey
126
+ expect(code).not.toContain("partitionKey: '{id}'");
127
+ // getById should use SDK query
128
+ expect(code).toContain("custom partition key");
129
+ expect(code).toContain("SELECT * FROM c WHERE c.id = @id");
130
+ // delete should read doc first to get PK value
131
+ expect(code).toContain("resources[0].tenantId");
132
+ expect(code).toContain("container.item(id, pkValue).delete()");
133
+ });
134
+
135
+ it("generates snapshot for custom partition key TS", () => {
136
+ const model = createBasicModelInfo({
137
+ partitionKey: "/tenantId",
138
+ fields: [
139
+ { name: "id", type: "string", isOptional: false, isArray: false },
140
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
141
+ { name: "title", type: "string", isOptional: false, isArray: false },
142
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
143
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
144
+ ],
145
+ });
146
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
147
+ expect(code).toMatchSnapshot();
148
+ });
149
+ });
150
+
151
+ describe("custom partition key (C#)", () => {
152
+ it("uses ReadItemStreamAsync when partitionKey is /id", () => {
153
+ const model = createBasicModelInfo({ partitionKey: "/id" });
154
+ const code = generateCSharpAzureFunctionsCRUD(model);
155
+ expect(code).toContain("ReadItemStreamAsync(id, new PartitionKey(id))");
156
+ expect(code).toContain("DeleteItemAsync<JsonObject>(id, new PartitionKey(id))");
157
+ });
158
+
159
+ it("uses query when partitionKey is not /id", () => {
160
+ const model = createBasicModelInfo({
161
+ partitionKey: "/tenantId",
162
+ fields: [
163
+ { name: "id", type: "string", isOptional: false, isArray: false },
164
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
165
+ { name: "title", type: "string", isOptional: false, isArray: false },
166
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
167
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
168
+ ],
169
+ });
170
+ const code = generateCSharpAzureFunctionsCRUD(model);
171
+ // ReadCosmosItemAsync should use query instead of point read
172
+ expect(code).toContain("GetItemQueryStreamIterator(query)");
173
+ expect(code).not.toContain("ReadItemStreamAsync(id, new PartitionKey(id))");
174
+ // Delete should read doc first for PK value
175
+ expect(code).toContain('existing["tenantId"]');
176
+ });
177
+
178
+ it("generates snapshot for custom partition key C#", () => {
179
+ const model = createBasicModelInfo({
180
+ partitionKey: "/tenantId",
181
+ fields: [
182
+ { name: "id", type: "string", isOptional: false, isArray: false },
183
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
184
+ { name: "title", type: "string", isOptional: false, isArray: false },
185
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
186
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
187
+ ],
188
+ });
189
+ const code = generateCSharpAzureFunctionsCRUD(model);
190
+ expect(code).toMatchSnapshot();
191
+ });
192
+ });
193
+
194
+ describe("custom partition key (Python)", () => {
195
+ it("uses read_item with partition_key=item_id when partitionKey is /id", () => {
196
+ const model = createBasicModelInfo({ partitionKey: "/id" });
197
+ const generated = generatePythonAzureFunctionsCRUD(model);
198
+ expect(generated.blueprint).toContain("partition_key=item_id");
199
+ });
200
+
201
+ it("uses cross-partition query when partitionKey is not /id", () => {
202
+ const model = createBasicModelInfo({
203
+ partitionKey: "/tenantId",
204
+ fields: [
205
+ { name: "id", type: "string", isOptional: false, isArray: false },
206
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
207
+ { name: "title", type: "string", isOptional: false, isArray: false },
208
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
209
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
210
+ ],
211
+ });
212
+ const generated = generatePythonAzureFunctionsCRUD(model);
213
+ // Should not use direct read_item with partition_key=item_id
214
+ expect(generated.blueprint).not.toContain("partition_key=item_id");
215
+ // Should use cross-partition query
216
+ expect(generated.blueprint).toContain("enable_cross_partition_query=True");
217
+ expect(generated.blueprint).toContain('SELECT * FROM c WHERE c.id = @id');
218
+ // Delete should get PK value from document
219
+ expect(generated.blueprint).toContain('.get("tenantId")');
220
+ });
221
+
222
+ it("generates snapshot for custom partition key Python", () => {
223
+ const model = createBasicModelInfo({
224
+ partitionKey: "/tenantId",
225
+ fields: [
226
+ { name: "id", type: "string", isOptional: false, isArray: false },
227
+ { name: "tenantId", type: "string", isOptional: false, isArray: false },
228
+ { name: "title", type: "string", isOptional: false, isArray: false },
229
+ { name: "createdAt", type: "string", isOptional: false, isArray: false },
230
+ { name: "updatedAt", type: "string", isOptional: false, isArray: false },
231
+ ],
232
+ });
233
+ const generated = generatePythonAzureFunctionsCRUD(model);
234
+ expect(generated.blueprint).toMatchSnapshot();
235
+ });
236
+ });
237
+ });
@@ -0,0 +1,115 @@
1
+ import {
2
+ buildGeneratedProjectDependencies,
3
+ buildGeneratedProjectDevDependencies,
4
+ buildSwallowKitMcpProjectConfigSource,
5
+ buildCSharpFunctionsProgramSource,
6
+ buildCSharpFunctionsProjectSource,
7
+ buildSwallowKitConfigSource,
8
+ injectSwallowKitNextConfig,
9
+ } from "../cli/commands/init";
10
+
11
+ describe("injectSwallowKitNextConfig", () => {
12
+ it("adds standalone settings without deprecated experimental options", () => {
13
+ const original = `import type { NextConfig } from "next";
14
+
15
+ const nextConfig: NextConfig = {
16
+ /* config options here */
17
+ };
18
+
19
+ export default nextConfig;
20
+ `;
21
+
22
+ const updated = injectSwallowKitNextConfig(original, "sample-app");
23
+
24
+ expect(updated).toContain("output: 'standalone'");
25
+ expect(updated).toContain("transpilePackages: ['@sample-app/shared']");
26
+ expect(updated).toContain("serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers']");
27
+ expect(updated).not.toContain("turbopackUseSystemTlsCerts");
28
+ expect(updated).not.toContain("experimental:");
29
+ });
30
+
31
+ it("supports JavaScript next.config format", () => {
32
+ const original = `const nextConfig = {
33
+ /* config options here */
34
+ };
35
+
36
+ module.exports = nextConfig;
37
+ `;
38
+
39
+ const updated = injectSwallowKitNextConfig(original, "sample-app");
40
+
41
+ expect(updated).toContain("transpilePackages: ['@sample-app/shared']");
42
+ expect(updated).toContain("module.exports = nextConfig;");
43
+ });
44
+
45
+ it("generates a C# Program.cs compatible with the current worker packages", () => {
46
+ const source = buildCSharpFunctionsProgramSource();
47
+
48
+ expect(source).toContain("new HostBuilder()");
49
+ expect(source).toContain(".ConfigureFunctionsWorkerDefaults()");
50
+ expect(source).toContain("services.AddApplicationInsightsTelemetryWorkerService()");
51
+ expect(source).toContain("services.ConfigureFunctionsApplicationInsights()");
52
+ expect(source).toContain("Microsoft.Azure.Functions.Worker.ApplicationInsights");
53
+ expect(source).not.toContain("Microsoft.Azure.Functions.Worker.Builder");
54
+ expect(source).not.toContain("FunctionsApplication.CreateBuilder");
55
+ });
56
+
57
+ it("excludes nested generated bin and obj files from the C# Functions project", () => {
58
+ const source = buildCSharpFunctionsProjectSource();
59
+
60
+ expect(source).toContain("<TargetFramework>net10.0</TargetFramework>");
61
+ expect(source).toContain('Version="2.52.0"');
62
+ expect(source).toContain('Version="3.3.0"');
63
+ expect(source).toContain('Version="2.0.7"');
64
+ expect(source).toContain('Version="2.50.0"');
65
+ expect(source).toContain('<Compile Remove="generated\\**\\bin\\**\\*.cs;generated\\**\\obj\\**\\*.cs" />');
66
+ expect(source).toContain('<EmbeddedResource Remove="generated\\**\\bin\\**;generated\\**\\obj\\**" />');
67
+ expect(source).toContain('<None Remove="generated\\**\\bin\\**;generated\\**\\obj\\**" />');
68
+ });
69
+
70
+ it("builds swallowkit.config.js without a local swallowkit package type import", () => {
71
+ const source = buildSwallowKitConfigSource("typescript");
72
+
73
+ expect(source).toContain("language: 'typescript'");
74
+ expect(source).toContain("baseUrl: process.env.BACKEND_FUNCTIONS_BASE_URL");
75
+ expect(source).not.toContain("import('swallowkit').SwallowKitConfig");
76
+ });
77
+
78
+ it("does not add swallowkit as a generated project dependency", () => {
79
+ const dependencies = buildGeneratedProjectDependencies("sample-app");
80
+
81
+ expect(dependencies).toEqual({
82
+ "@azure/cosmos": "^4.0.0",
83
+ applicationinsights: "^3.3.0",
84
+ "@sample-app/shared": "*",
85
+ });
86
+ expect(dependencies).not.toHaveProperty("swallowkit");
87
+ });
88
+
89
+ it("adds swallowkit as a generated project devDependency", () => {
90
+ const devDependencies = buildGeneratedProjectDevDependencies();
91
+
92
+ expect(devDependencies).toEqual({
93
+ swallowkit: expect.stringMatching(/.+/),
94
+ });
95
+ });
96
+
97
+ it("builds a project-scoped MCP config that launches the local swallowkit-mcp entrypoint", () => {
98
+ const source = buildSwallowKitMcpProjectConfigSource();
99
+ const parsed = JSON.parse(source) as {
100
+ mcpServers: {
101
+ swallowkit: {
102
+ command: string;
103
+ args: string[];
104
+ cwd?: string;
105
+ };
106
+ };
107
+ };
108
+
109
+ expect(parsed.mcpServers.swallowkit.command).toBe("node");
110
+ expect(parsed.mcpServers.swallowkit.args).toEqual([
111
+ expect.stringMatching(/^\.\/node_modules\/swallowkit\/dist\/mcp\/index\.js$/),
112
+ ]);
113
+ expect(parsed.mcpServers.swallowkit.cwd).toBe(".");
114
+ });
115
+ });
@@ -0,0 +1,251 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { runMachineCli } from "../machine";
5
+
6
+ const repoRoot = process.cwd();
7
+
8
+ function writeFile(filePath: string, content: string): void {
9
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
+ fs.writeFileSync(filePath, content, "utf-8");
11
+ }
12
+
13
+ function createModelSource(name: string): string {
14
+ return `import { z } from 'zod/v4';
15
+
16
+ export const ${name} = z.object({
17
+ id: z.string(),
18
+ name: z.string().min(1),
19
+ createdAt: z.string().optional(),
20
+ updatedAt: z.string().optional(),
21
+ });
22
+
23
+ export type ${name} = z.infer<typeof ${name}>;
24
+
25
+ export const displayName = '${name}';
26
+ `;
27
+ }
28
+
29
+ function createProjectFixture(rootDir: string, options: { includeGeneratedArtifacts?: boolean; forbiddenBffDependency?: boolean } = {}): void {
30
+ writeFile(path.join(rootDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
31
+ writeFile(
32
+ path.join(rootDir, "swallowkit.config.js"),
33
+ `module.exports = {
34
+ database: {
35
+ connectionString: 'AccountEndpoint=https://example.local;',
36
+ },
37
+ backend: {
38
+ language: 'typescript',
39
+ },
40
+ api: {
41
+ endpoint: '/api/_swallowkit',
42
+ },
43
+ };
44
+ `
45
+ );
46
+ writeFile(path.join(rootDir, "shared", "package.json"), JSON.stringify({ name: "@sample-app/shared" }, null, 2));
47
+ writeFile(path.join(rootDir, "shared", "index.ts"), "export {};\n");
48
+ writeFile(path.join(rootDir, "shared", "models", "todo.ts"), createModelSource("Todo"));
49
+ fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
50
+ fs.symlinkSync(
51
+ path.join(repoRoot, "node_modules", "zod"),
52
+ path.join(rootDir, "node_modules", "zod"),
53
+ "junction"
54
+ );
55
+
56
+ if (options.includeGeneratedArtifacts) {
57
+ writeFile(path.join(rootDir, "lib", "api", "call-function.ts"), "export function callFunction() {}\n");
58
+ writeFile(path.join(rootDir, "functions", "src", "todo.ts"), "export {};\n");
59
+ writeFile(path.join(rootDir, "app", "api", "todo", "route.ts"), "export async function GET() { return Response.json([]); }\n");
60
+ writeFile(path.join(rootDir, "app", "api", "todo", "[id]", "route.ts"), "export async function GET() { return Response.json({}); }\n");
61
+ }
62
+
63
+ if (options.forbiddenBffDependency) {
64
+ writeFile(
65
+ path.join(rootDir, "app", "api", "forbidden", "route.ts"),
66
+ "import { CosmosClient } from '@azure/cosmos';\nexport async function GET() { return Response.json({ ok: Boolean(CosmosClient) }); }\n"
67
+ );
68
+ }
69
+ }
70
+
71
+ function createFreshInitProjectFixture(rootDir: string, backendLanguage: "csharp" | "python" = "csharp"): void {
72
+ writeFile(path.join(rootDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
73
+ writeFile(
74
+ path.join(rootDir, "swallowkit.config.js"),
75
+ `module.exports = {
76
+ backend: {
77
+ language: '${backendLanguage}',
78
+ },
79
+ functions: {
80
+ baseUrl: process.env.BACKEND_FUNCTIONS_BASE_URL || process.env.FUNCTIONS_BASE_URL || 'http://localhost:7071',
81
+ },
82
+ deployment: {
83
+ resourceGroup: process.env.AZURE_RESOURCE_GROUP || '',
84
+ swaName: process.env.AZURE_SWA_NAME || '',
85
+ },
86
+ };
87
+ `
88
+ );
89
+ writeFile(path.join(rootDir, "shared", "package.json"), JSON.stringify({ name: "@sample-app/shared" }, null, 2));
90
+ writeFile(path.join(rootDir, "shared", "index.ts"), "export {};\n");
91
+ fs.mkdirSync(path.join(rootDir, "shared", "models"), { recursive: true });
92
+ writeFile(path.join(rootDir, "app", "api", "greet", "route.ts"), "export async function GET() { return Response.json({}); }\n");
93
+ writeFile(path.join(rootDir, "lib", "api", "backend.ts"), "export const api = {};\n");
94
+ fs.mkdirSync(path.join(rootDir, "functions"), { recursive: true });
95
+ }
96
+
97
+ async function runMachine(argv: string[]): Promise<{ response: any; exitCode: number }> {
98
+ const writes: string[] = [];
99
+ const originalWrite = process.stdout.write.bind(process.stdout);
100
+ const originalExitCode = process.exitCode;
101
+
102
+ (process.stdout.write as unknown as (chunk: string | Uint8Array) => boolean) = ((chunk: string | Uint8Array) => {
103
+ writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
104
+ return true;
105
+ }) as typeof process.stdout.write;
106
+
107
+ process.exitCode = 0;
108
+
109
+ try {
110
+ await runMachineCli(argv);
111
+ return {
112
+ response: JSON.parse(writes.join("")),
113
+ exitCode: process.exitCode || 0,
114
+ };
115
+ } finally {
116
+ process.stdout.write = originalWrite;
117
+ process.exitCode = originalExitCode;
118
+ }
119
+ }
120
+
121
+ describe("machine CLI", () => {
122
+ const originalCwd = process.cwd();
123
+ let tempDir: string;
124
+
125
+ beforeEach(() => {
126
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-machine-"));
127
+ process.chdir(tempDir);
128
+ });
129
+
130
+ afterEach(() => {
131
+ process.chdir(originalCwd);
132
+ fs.rmSync(tempDir, { recursive: true, force: true });
133
+ });
134
+
135
+ it("inspects SwallowKit project metadata as JSON", async () => {
136
+ createProjectFixture(tempDir, { includeGeneratedArtifacts: true });
137
+
138
+ const { response, exitCode } = await runMachine(["node", "swallowkit", "machine", "inspect", "project"]);
139
+
140
+ expect(exitCode).toBe(0);
141
+ expect(response.ok).toBe(true);
142
+ expect(response.command).toBe("inspect-project");
143
+ expect(response.data.manifest.entities[0].name).toBe("Todo");
144
+ expect(response.data.manifest.routes).toEqual(
145
+ expect.arrayContaining([
146
+ expect.objectContaining({
147
+ name: "Todo-bff-list",
148
+ publicPath: "/api/todo",
149
+ exists: true,
150
+ }),
151
+ ])
152
+ );
153
+ });
154
+
155
+ it("validates forbidden BFF dependencies", async () => {
156
+ createProjectFixture(tempDir, {
157
+ includeGeneratedArtifacts: true,
158
+ forbiddenBffDependency: true,
159
+ });
160
+
161
+ const { response, exitCode } = await runMachine(["node", "swallowkit", "machine", "validate", "project"]);
162
+
163
+ expect(exitCode).toBe(0);
164
+ expect(response.ok).toBe(true);
165
+ expect(response.command).toBe("validate-project");
166
+ expect(response.data.violations).toEqual(
167
+ expect.arrayContaining([
168
+ expect.objectContaining({
169
+ code: "forbidden-bff-dependency",
170
+ severity: "error",
171
+ }),
172
+ ])
173
+ );
174
+ });
175
+
176
+ it("does not report false positives for a fresh C# init project", async () => {
177
+ createFreshInitProjectFixture(tempDir, "csharp");
178
+
179
+ const { response, exitCode } = await runMachine(["node", "swallowkit", "machine", "validate", "project"]);
180
+
181
+ expect(exitCode).toBe(0);
182
+ expect(response.ok).toBe(true);
183
+ expect(response.command).toBe("validate-project");
184
+ expect(response.data.manifest.backendLanguage).toBe("csharp");
185
+ expect(response.data.manifest.configValidation.valid).toBe(true);
186
+ expect(response.data.violations).toEqual([]);
187
+ });
188
+
189
+ it("generates model templates through the machine interface", async () => {
190
+ writeFile(path.join(tempDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
191
+ writeFile(
192
+ path.join(tempDir, "swallowkit.config.js"),
193
+ `module.exports = {
194
+ backend: { language: 'typescript' },
195
+ api: { endpoint: '/api/_swallowkit' },
196
+ };
197
+ `
198
+ );
199
+ fs.mkdirSync(path.join(tempDir, "node_modules"), { recursive: true });
200
+ fs.symlinkSync(
201
+ path.join(repoRoot, "node_modules", "zod"),
202
+ path.join(tempDir, "node_modules", "zod"),
203
+ "junction"
204
+ );
205
+ writeFile(path.join(tempDir, "shared", "index.ts"), "export {};\n");
206
+
207
+ const { response, exitCode } = await runMachine([
208
+ "node",
209
+ "swallowkit",
210
+ "machine",
211
+ "generate",
212
+ "model",
213
+ "Task",
214
+ "--overwrite",
215
+ "never",
216
+ ]);
217
+
218
+ expect(exitCode).toBe(0);
219
+ expect(response.ok).toBe(true);
220
+ expect(response.command).toBe("generate-model");
221
+ expect(response.data.createdFiles).toContain("shared/models/task.ts");
222
+ expect(fs.existsSync(path.join(tempDir, ".swallowkit", "project.json"))).toBe(true);
223
+ });
224
+
225
+ it("generates scaffold artifacts through the machine interface", async () => {
226
+ createProjectFixture(tempDir);
227
+
228
+ const { response, exitCode } = await runMachine([
229
+ "node",
230
+ "swallowkit",
231
+ "machine",
232
+ "generate",
233
+ "scaffold",
234
+ "todo",
235
+ "--api-only",
236
+ ]);
237
+
238
+ expect(exitCode).toBe(0);
239
+ expect(response.ok).toBe(true);
240
+ expect(response.command).toBe("generate-scaffold");
241
+ expect(response.data.createdFiles).toEqual(
242
+ expect.arrayContaining([
243
+ "app/api/todo/route.ts",
244
+ "app/api/todo/[id]/route.ts",
245
+ "functions/src/todo.ts",
246
+ "lib/api/call-function.ts",
247
+ ])
248
+ );
249
+ expect(fs.existsSync(path.join(tempDir, ".swallowkit", "project.json"))).toBe(true);
250
+ });
251
+ });