swallowkit 1.0.0-beta.6 → 1.0.0-beta.61

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 (296) hide show
  1. package/README.ja.md +87 -175
  2. package/README.md +87 -176
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +62 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  25. package/dist/cli/commands/dev-seeds.js +474 -0
  26. package/dist/cli/commands/dev-seeds.js.map +1 -0
  27. package/dist/cli/commands/dev.d.ts +38 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +733 -155
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/index.d.ts +1 -0
  32. package/dist/cli/commands/index.d.ts.map +1 -1
  33. package/dist/cli/commands/index.js +3 -1
  34. package/dist/cli/commands/index.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +74 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +1376 -930
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/scaffold.d.ts +24 -0
  40. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  41. package/dist/cli/commands/scaffold.js +473 -90
  42. package/dist/cli/commands/scaffold.js.map +1 -1
  43. package/dist/cli/index.d.ts +5 -1
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +335 -55
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/core/config.d.ts +14 -3
  48. package/dist/core/config.d.ts.map +1 -1
  49. package/dist/core/config.js +179 -8
  50. package/dist/core/config.js.map +1 -1
  51. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  52. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  53. package/dist/core/mock/connector-mock-server.js +480 -0
  54. package/dist/core/mock/connector-mock-server.js.map +1 -0
  55. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  56. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  57. package/dist/core/mock/zod-mock-generator.js +163 -0
  58. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  59. package/dist/core/operations/auth-operations.d.ts +61 -0
  60. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  61. package/dist/core/operations/auth-operations.js +272 -0
  62. package/dist/core/operations/auth-operations.js.map +1 -0
  63. package/dist/core/operations/create-model.d.ts +15 -0
  64. package/dist/core/operations/create-model.d.ts.map +1 -0
  65. package/dist/core/operations/create-model.js +185 -0
  66. package/dist/core/operations/create-model.js.map +1 -0
  67. package/dist/core/operations/file-session.d.ts +56 -0
  68. package/dist/core/operations/file-session.d.ts.map +1 -0
  69. package/dist/core/operations/file-session.js +203 -0
  70. package/dist/core/operations/file-session.js.map +1 -0
  71. package/dist/core/operations/provision-operations.d.ts +52 -0
  72. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  73. package/dist/core/operations/provision-operations.js +211 -0
  74. package/dist/core/operations/provision-operations.js.map +1 -0
  75. package/dist/core/operations/runtime.d.ts +37 -0
  76. package/dist/core/operations/runtime.d.ts.map +1 -0
  77. package/dist/core/operations/runtime.js +248 -0
  78. package/dist/core/operations/runtime.js.map +1 -0
  79. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  80. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-apply.js +57 -0
  82. package/dist/core/operations/scaffold-apply.js.map +1 -0
  83. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  84. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-machine.js +51 -0
  86. package/dist/core/operations/scaffold-machine.js.map +1 -0
  87. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  88. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  89. package/dist/core/operations/scaffold-plan.js +187 -0
  90. package/dist/core/operations/scaffold-plan.js.map +1 -0
  91. package/dist/core/project/agent-instructions.d.ts +29 -0
  92. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  93. package/dist/core/project/agent-instructions.js +160 -0
  94. package/dist/core/project/agent-instructions.js.map +1 -0
  95. package/dist/core/project/artifacts.d.ts +58 -0
  96. package/dist/core/project/artifacts.d.ts.map +1 -0
  97. package/dist/core/project/artifacts.js +179 -0
  98. package/dist/core/project/artifacts.js.map +1 -0
  99. package/dist/core/project/boundaries.d.ts +50 -0
  100. package/dist/core/project/boundaries.d.ts.map +1 -0
  101. package/dist/core/project/boundaries.js +117 -0
  102. package/dist/core/project/boundaries.js.map +1 -0
  103. package/dist/core/project/capabilities.d.ts +58 -0
  104. package/dist/core/project/capabilities.d.ts.map +1 -0
  105. package/dist/core/project/capabilities.js +135 -0
  106. package/dist/core/project/capabilities.js.map +1 -0
  107. package/dist/core/project/drift.d.ts +29 -0
  108. package/dist/core/project/drift.d.ts.map +1 -0
  109. package/dist/core/project/drift.js +188 -0
  110. package/dist/core/project/drift.js.map +1 -0
  111. package/dist/core/project/infra.d.ts +49 -0
  112. package/dist/core/project/infra.d.ts.map +1 -0
  113. package/dist/core/project/infra.js +167 -0
  114. package/dist/core/project/infra.js.map +1 -0
  115. package/dist/core/project/manifest.d.ts +92 -0
  116. package/dist/core/project/manifest.d.ts.map +1 -0
  117. package/dist/core/project/manifest.js +321 -0
  118. package/dist/core/project/manifest.js.map +1 -0
  119. package/dist/core/project/state.d.ts +15 -0
  120. package/dist/core/project/state.d.ts.map +1 -0
  121. package/dist/core/project/state.js +106 -0
  122. package/dist/core/project/state.js.map +1 -0
  123. package/dist/core/project/validation.d.ts +20 -0
  124. package/dist/core/project/validation.d.ts.map +1 -0
  125. package/dist/core/project/validation.js +274 -0
  126. package/dist/core/project/validation.js.map +1 -0
  127. package/dist/core/project/workflows.d.ts +43 -0
  128. package/dist/core/project/workflows.d.ts.map +1 -0
  129. package/dist/core/project/workflows.js +299 -0
  130. package/dist/core/project/workflows.js.map +1 -0
  131. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  132. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/auth-generator.js +1774 -0
  134. package/dist/core/scaffold/auth-generator.js.map +1 -0
  135. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  136. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  137. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  138. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  139. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  140. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  141. package/dist/core/scaffold/functions-generator.js +771 -69
  142. package/dist/core/scaffold/functions-generator.js.map +1 -1
  143. package/dist/core/scaffold/model-parser.d.ts +21 -1
  144. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  145. package/dist/core/scaffold/model-parser.js +230 -79
  146. package/dist/core/scaffold/model-parser.js.map +1 -1
  147. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  148. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  149. package/dist/core/scaffold/native-schema-generator.js +753 -0
  150. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  151. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  152. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/nextjs-generator.js +153 -9
  154. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  155. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  156. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  157. package/dist/core/scaffold/openapi-generator.js +190 -0
  158. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  159. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  160. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  161. package/dist/core/scaffold/ui-generator.js +174 -34
  162. package/dist/core/scaffold/ui-generator.js.map +1 -1
  163. package/dist/core/verify/index.d.ts +69 -0
  164. package/dist/core/verify/index.d.ts.map +1 -0
  165. package/dist/core/verify/index.js +412 -0
  166. package/dist/core/verify/index.js.map +1 -0
  167. package/dist/database/base-model.d.ts +3 -3
  168. package/dist/database/base-model.js +3 -3
  169. package/dist/index.d.ts +2 -2
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +2 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/machine/contracts.d.ts +24 -0
  174. package/dist/machine/contracts.d.ts.map +1 -0
  175. package/dist/machine/contracts.js +3 -0
  176. package/dist/machine/contracts.js.map +1 -0
  177. package/dist/machine/errors.d.ts +14 -0
  178. package/dist/machine/errors.d.ts.map +1 -0
  179. package/dist/machine/errors.js +42 -0
  180. package/dist/machine/errors.js.map +1 -0
  181. package/dist/machine/index.d.ts +3 -0
  182. package/dist/machine/index.d.ts.map +1 -0
  183. package/dist/machine/index.js +566 -0
  184. package/dist/machine/index.js.map +1 -0
  185. package/dist/mcp/index.d.ts +25 -0
  186. package/dist/mcp/index.d.ts.map +1 -0
  187. package/dist/mcp/index.js +425 -0
  188. package/dist/mcp/index.js.map +1 -0
  189. package/dist/types/index.d.ts +109 -0
  190. package/dist/types/index.d.ts.map +1 -1
  191. package/dist/utils/package-manager.d.ts +9 -12
  192. package/dist/utils/package-manager.d.ts.map +1 -1
  193. package/dist/utils/package-manager.js +51 -22
  194. package/dist/utils/package-manager.js.map +1 -1
  195. package/dist/utils/python-uv.d.ts +21 -0
  196. package/dist/utils/python-uv.d.ts.map +1 -0
  197. package/dist/utils/python-uv.js +111 -0
  198. package/dist/utils/python-uv.js.map +1 -0
  199. package/dist/version.d.ts +2 -0
  200. package/dist/version.d.ts.map +1 -0
  201. package/dist/version.js +49 -0
  202. package/dist/version.js.map +1 -0
  203. package/package.json +16 -17
  204. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  205. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
  206. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  207. package/src/__tests__/agent-instructions.test.ts +237 -0
  208. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  209. package/src/__tests__/auth.test.ts +770 -0
  210. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  211. package/src/__tests__/config.test.ts +295 -0
  212. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  213. package/src/__tests__/connector-mock-server.test.ts +459 -0
  214. package/src/__tests__/connector-model-bff.test.ts +162 -0
  215. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  216. package/src/__tests__/dev-seeds.test.ts +236 -0
  217. package/src/__tests__/dev.test.ts +296 -0
  218. package/src/__tests__/file-session.test.ts +98 -0
  219. package/src/__tests__/fixtures.ts +184 -0
  220. package/src/__tests__/functions-generator.test.ts +237 -0
  221. package/src/__tests__/init.test.ts +363 -0
  222. package/src/__tests__/jest-globals.d.ts +8 -0
  223. package/src/__tests__/machine.test.ts +371 -0
  224. package/src/__tests__/mcp.test.ts +253 -0
  225. package/src/__tests__/model-parser.test.ts +142 -0
  226. package/src/__tests__/multi-auth.test.ts +160 -0
  227. package/src/__tests__/nextjs-generator.test.ts +105 -0
  228. package/src/__tests__/openapi-generator.test.ts +43 -0
  229. package/src/__tests__/package-manager.test.ts +244 -0
  230. package/src/__tests__/plan-apply.test.ts +499 -0
  231. package/src/__tests__/python-uv.test.ts +48 -0
  232. package/src/__tests__/scaffold.test.ts +302 -0
  233. package/src/__tests__/string-utils.test.ts +75 -0
  234. package/src/__tests__/tsconfig.json +15 -0
  235. package/src/__tests__/ui-generator.test.ts +248 -0
  236. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  237. package/src/cli/commands/add-auth.ts +853 -0
  238. package/src/cli/commands/add-connector.ts +159 -0
  239. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  240. package/src/cli/commands/create-model.ts +62 -0
  241. package/src/cli/commands/dev-seeds.ts +629 -0
  242. package/src/cli/commands/dev.ts +1252 -0
  243. package/src/cli/commands/index.ts +9 -0
  244. package/src/cli/commands/init.ts +3414 -0
  245. package/src/cli/commands/provision.ts +193 -0
  246. package/src/cli/commands/scaffold.ts +1081 -0
  247. package/src/cli/index.ts +352 -0
  248. package/src/core/config.ts +380 -0
  249. package/src/core/mock/connector-mock-server.ts +555 -0
  250. package/src/core/mock/zod-mock-generator.ts +205 -0
  251. package/src/core/operations/auth-operations.ts +360 -0
  252. package/src/core/operations/create-model.ts +191 -0
  253. package/src/core/operations/file-session.ts +222 -0
  254. package/src/core/operations/provision-operations.ts +297 -0
  255. package/src/core/operations/runtime.ts +258 -0
  256. package/src/core/operations/scaffold-apply.ts +106 -0
  257. package/src/core/operations/scaffold-machine.ts +77 -0
  258. package/src/core/operations/scaffold-plan.ts +215 -0
  259. package/src/core/project/agent-instructions.ts +167 -0
  260. package/src/core/project/agent-templates/AGENTS.md +504 -0
  261. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  262. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  263. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  264. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  265. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  266. package/src/core/project/artifacts.ts +205 -0
  267. package/src/core/project/boundaries.ts +170 -0
  268. package/src/core/project/capabilities.ts +193 -0
  269. package/src/core/project/drift.ts +202 -0
  270. package/src/core/project/infra.ts +196 -0
  271. package/src/core/project/manifest.ts +402 -0
  272. package/src/core/project/state.ts +77 -0
  273. package/src/core/project/validation.ts +295 -0
  274. package/src/core/project/workflows.ts +295 -0
  275. package/src/core/scaffold/auth-generator.ts +1824 -0
  276. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  277. package/src/core/scaffold/functions-generator.ts +972 -0
  278. package/src/core/scaffold/model-parser.ts +781 -0
  279. package/src/core/scaffold/native-schema-generator.ts +904 -0
  280. package/src/core/scaffold/nextjs-generator.ts +382 -0
  281. package/src/core/scaffold/openapi-generator.ts +212 -0
  282. package/src/core/scaffold/ui-generator.ts +1100 -0
  283. package/src/core/verify/index.ts +468 -0
  284. package/src/database/base-model.ts +184 -0
  285. package/src/database/client.ts +140 -0
  286. package/src/database/repository.ts +104 -0
  287. package/src/database/runtime-check.ts +25 -0
  288. package/src/index.ts +27 -0
  289. package/src/machine/contracts.ts +33 -0
  290. package/src/machine/errors.ts +45 -0
  291. package/src/machine/index.ts +690 -0
  292. package/src/mcp/index.ts +423 -0
  293. package/src/types/index.ts +182 -0
  294. package/src/utils/package-manager.ts +251 -0
  295. package/src/utils/python-uv.ts +96 -0
  296. package/src/version.ts +13 -0
@@ -0,0 +1,142 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { parseModelFile } from "../core/scaffold/model-parser";
4
+
5
+ describe("parseModelFile", () => {
6
+ afterEach(() => { jest.restoreAllMocks(); });
7
+
8
+ it.each(["zod", "zod/v4"])("executes TypeScript modules and nested imports with %s without fallback", async (zodImport) => {
9
+ const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-model-parser-"));
10
+ const warning = jest.spyOn(console, "warn").mockImplementation(() => {});
11
+ try {
12
+ fs.writeFileSync(path.join(tempDir, "leaf.ts"), `
13
+ import { z } from '${zodImport}';
14
+ export const displayName = 'Leaf';
15
+ export const leafSchema = z.object({ name: z.string() });
16
+ `);
17
+ fs.writeFileSync(path.join(tempDir, "nested.ts"), `
18
+ import { z } from '${zodImport}';
19
+ import { leafSchema } from './leaf';
20
+ export const displayName = 'Nested';
21
+ export const nestedSchema = z.object({ leaf: leafSchema });
22
+ `);
23
+ const modelPath = path.join(tempDir, "record.ts");
24
+ fs.writeFileSync(modelPath, `
25
+ import { z } from '${zodImport}';
26
+ import { nestedSchema } from './nested';
27
+ import { leafSchema as otherSchema } from './leaf';
28
+ interface Settings { inner: { count: number }; }
29
+ type Label = string;
30
+ const label: Label = 'https://example.com';
31
+ const statuses = ['draft', 'active'] as const;
32
+ export const recordSchema = z.object({
33
+ id: z.string(), title: z.string().default(label), count: z.number(), enabled: z.boolean(),
34
+ optional: z.string().optional(), nullable: z.number().nullable(), status: z.enum(statuses),
35
+ numbers: z.array(z.number()), nested: nestedSchema, others: z.array(otherSchema),
36
+ inline: z.object({ name: z.string() }), tenantId: z.string(),
37
+ });
38
+ export type Record = z.infer<typeof recordSchema>;
39
+ export const displayName = 'Record';
40
+ export const partitionKey = '/tenantId';
41
+ export const connectorConfig = { connector: 'catalog', table: 'records', operations: ['read'] } as const;
42
+ export const authPolicy = { read: ['reader'], write: ['editor'] } as const;
43
+ `);
44
+ const model = await parseModelFile(modelPath);
45
+ expect(warning).not.toHaveBeenCalled();
46
+ expect(model.fields).toHaveLength(12);
47
+ expect(model.fields).toEqual(expect.arrayContaining([
48
+ expect.objectContaining({ name: "count", type: "number" }),
49
+ expect.objectContaining({ name: "enabled", type: "boolean" }),
50
+ expect.objectContaining({ name: "title", isOptional: true }),
51
+ expect.objectContaining({ name: "optional", isOptional: true }),
52
+ expect.objectContaining({ name: "nullable", isNullable: true }),
53
+ expect.objectContaining({ name: "status", enumValues: ["draft", "active"] }),
54
+ expect.objectContaining({ name: "numbers", type: "number", isArray: true }),
55
+ expect.objectContaining({ name: "nested", type: "object" }),
56
+ expect.objectContaining({ name: "others", type: "object", isArray: true }),
57
+ ]));
58
+ expect(model.connectorConfig).toEqual(expect.objectContaining({ connector: "catalog", table: "records", operations: ["read"] }));
59
+ expect(model.partitionKey).toBe("/tenantId");
60
+ expect(model.authPolicy).toEqual({ read: ["reader"], write: ["editor"] });
61
+ expect(fs.readdirSync(tempDir).some(name => name.startsWith(".swallowkit-parser-"))).toBe(false);
62
+ } finally {
63
+ fs.rmSync(tempDir, { recursive: true, force: true });
64
+ }
65
+ });
66
+
67
+ it("preserves array and enum metadata for schemas with defaults", async () => {
68
+ const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-model-parser-"));
69
+ const modelPath = path.join(tempDir, "product.ts");
70
+
71
+ try {
72
+ fs.writeFileSync(
73
+ modelPath,
74
+ `import { z } from 'zod/v4';
75
+
76
+ export const Product = z.object({
77
+ id: z.string(),
78
+ name: z.string().min(1),
79
+ price: z.number().min(0),
80
+ tags: z.array(z.string()).default([]),
81
+ status: z.enum(['draft', 'active', 'archived']).default('draft'),
82
+ createdAt: z.string().optional(),
83
+ updatedAt: z.string().optional(),
84
+ });
85
+
86
+ export type Product = z.infer<typeof Product>;
87
+ `,
88
+ "utf-8"
89
+ );
90
+
91
+ const model = await parseModelFile(modelPath);
92
+ expect(model.fields).toEqual(
93
+ expect.arrayContaining([
94
+ expect.objectContaining({
95
+ name: "tags",
96
+ type: "string",
97
+ isArray: true,
98
+ isOptional: true,
99
+ }),
100
+ expect.objectContaining({
101
+ name: "status",
102
+ type: "string",
103
+ isArray: false,
104
+ isOptional: true,
105
+ enumValues: ["draft", "active", "archived"],
106
+ }),
107
+ ])
108
+ );
109
+ } finally {
110
+ fs.rmSync(tempDir, { recursive: true, force: true });
111
+ }
112
+ });
113
+
114
+ it("distinguishes optional and nullable fields", async () => {
115
+ const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-model-parser-"));
116
+ const modelPath = path.join(tempDir, "coupon.ts");
117
+
118
+ try {
119
+ fs.writeFileSync(
120
+ modelPath,
121
+ `import { z } from 'zod/v4';
122
+
123
+ export const couponSchema = z.object({
124
+ requiredUserId: z.string(),
125
+ optionalUserId: z.string().optional(),
126
+ nullableUserId: z.string().nullable(),
127
+ });
128
+ `,
129
+ "utf-8"
130
+ );
131
+
132
+ const model = await parseModelFile(modelPath);
133
+ expect(model.fields).toEqual(expect.arrayContaining([
134
+ expect.objectContaining({ name: "requiredUserId", isOptional: false, isNullable: false }),
135
+ expect.objectContaining({ name: "optionalUserId", isOptional: true, isNullable: false }),
136
+ expect.objectContaining({ name: "nullableUserId", isOptional: false, isNullable: true }),
137
+ ]));
138
+ } finally {
139
+ fs.rmSync(tempDir, { recursive: true, force: true });
140
+ }
141
+ });
142
+ });
@@ -0,0 +1,160 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { addAuthCommand } from "../cli/commands/add-auth";
5
+ import { createUIAuthOptions } from "../cli/commands/scaffold";
6
+ import {
7
+ generateBFFCallFunctionWithMultipleAuth,
8
+ generateNamedAuthRouterCSharp,
9
+ generateNamedAuthRouterPython,
10
+ generateNamedAuthRouterTS,
11
+ } from "../core/scaffold/auth-generator";
12
+ import { normalizeAuthConfig } from "../core/config";
13
+ import { parseAuthPolicy } from "../core/scaffold/model-parser";
14
+ import { createBasicModelInfo } from "./fixtures";
15
+ import { generateCSharpAzureFunctionsCRUD, generateCompactAzureFunctionsCRUD, generatePythonAzureFunctionsCRUD } from "../core/scaffold/functions-generator";
16
+ import { generateAuthLayout, generateNewPage } from "../core/scaffold/ui-generator";
17
+ import { AuthConfig } from "../types";
18
+
19
+ const namedAuthConfig: AuthConfig = {
20
+ schemes: {
21
+ admin: { provider: "swa" },
22
+ lineUser: { provider: "external-token" },
23
+ },
24
+ authorization: {
25
+ defaultPolicy: "anonymous",
26
+ policies: {
27
+ adminOnly: { schemes: ["admin"], roles: ["authenticated"] },
28
+ lineUserOnly: { schemes: ["lineUser"], roles: ["authenticated"] },
29
+ },
30
+ },
31
+ };
32
+ const auth = normalizeAuthConfig(namedAuthConfig)!;
33
+
34
+ describe("named authentication routers", () => {
35
+ it("generates deterministic TypeScript policy routing and normalized principals", () => {
36
+ const code = generateNamedAuthRouterTS(auth);
37
+ expect(code).toContain("policy.schemes.filter");
38
+ expect(code).toContain("candidates.length !== 1");
39
+ expect(code).toContain("subject: value.userId");
40
+ expect(code).toContain("scheme + ':' + principal.subject");
41
+ expect(code).toContain("new AuthError(403");
42
+ expect(code).toContain("new AuthError(503");
43
+ expect(code).not.toContain("JWT_SECRET");
44
+ });
45
+
46
+ it("generates equivalent C# and Python named guards", () => {
47
+ const csharp = generateNamedAuthRouterCSharp(auth);
48
+ const python = generateNamedAuthRouterPython(auth);
49
+ expect(csharp).toContain("record AuthPrincipal(string Subject, string Scheme, string Issuer");
50
+ expect(csharp).toContain("candidates.Length != 1");
51
+ expect(csharp).toContain("HttpStatusCode.Forbidden");
52
+ expect(python).toContain('principal["scheme"] + ":" + principal["subject"]');
53
+ expect(python).toContain("len(candidates) != 1");
54
+ expect(python).toContain("AuthError(503");
55
+ });
56
+
57
+ it("generates valid branch structure for an SWA-only Python router", () => {
58
+ const swaOnly = normalizeAuthConfig({ schemes: { admin: { provider: "swa", swa: { allowedProviders: ["github"] } } }, authorization: { defaultPolicy: "anonymous", policies: { adminOnly: { schemes: ["admin"] } } } })!;
59
+ const python = generateNamedAuthRouterPython(swaOnly);
60
+ expect(python).toContain(' if PROVIDERS[scheme] == "swa":');
61
+ expect(python).not.toContain(' elif PROVIDERS[scheme] == "swa":');
62
+ expect(python).toContain("ALLOWED_PROVIDERS");
63
+ expect(generateNamedAuthRouterCSharp(swaOnly)).toContain("AllowedProviders[scheme]");
64
+ });
65
+
66
+ it("emits Array.Empty<string>() instead of untyped new[] { } for empty C# arrays (CS0826)", () => {
67
+ const emptyLists = normalizeAuthConfig({
68
+ schemes: { api: { provider: "external-token" } },
69
+ authorization: { defaultPolicy: "anonymous", policies: { apiOnly: { schemes: ["api"] } } },
70
+ })!;
71
+ const csharp = generateNamedAuthRouterCSharp(emptyLists);
72
+ expect(csharp).not.toMatch(/new\[\]\s*\{\s*\}/);
73
+ expect(csharp).toContain("Array.Empty<string>()");
74
+ // 非空のリストは従来通り new[] リテラル
75
+ expect(csharp).toContain('new[] { "api" }');
76
+ });
77
+
78
+ it("scaffolds named read/write guards for all Functions languages", () => {
79
+ const policy = { read: "adminOnly", write: "lineUserOnly" };
80
+ const model = createBasicModelInfo();
81
+ const ts = generateCompactAzureFunctionsCRUD(model, "@fixture/shared", policy);
82
+ const cs = generateCSharpAzureFunctionsCRUD(model, policy);
83
+ const py = generatePythonAzureFunctionsCRUD(model, policy).blueprint;
84
+ expect(ts).toContain("./auth/auth-router");
85
+ expect(ts).toContain('requireAuth(request, "adminOnly")');
86
+ expect(ts).toContain('requireAuth(request, "lineUserOnly")');
87
+ expect(cs).toContain('AuthRouter.Authorize(request, "adminOnly")');
88
+ expect(cs).toContain('AuthRouter.Authorize(request, "lineUserOnly")');
89
+ expect(py).toContain("from auth.auth_router import");
90
+ expect(py).toContain('require_auth(req, "adminOnly")');
91
+ expect(py).toContain('require_auth(req, "lineUserOnly")');
92
+ });
93
+
94
+ it("forwards both credential sources and the Functions host key", () => {
95
+ const code = generateBFFCallFunctionWithMultipleAuth();
96
+ expect(code).toContain("fetchHeaders.Authorization");
97
+ expect(code).toContain("fetchHeaders['x-ms-client-principal']");
98
+ expect(code).toContain("'x-functions-key'");
99
+ expect(code).not.toMatch(/console\.log\([^\n]*(authorization|principal|cookie)/i);
100
+ });
101
+
102
+ it("parses model-wide and operation-specific named policies", () => {
103
+ expect(parseAuthPolicy("export const authPolicy = { policy: 'adminOnly' };"))
104
+ .toEqual({ policy: "adminOnly" });
105
+ expect(parseAuthPolicy("export const authPolicy = { read: 'adminOnly', write: 'lineUserOnly' };"))
106
+ .toEqual({ read: "adminOnly", write: "lineUserOnly" });
107
+ });
108
+
109
+ it("resolves the same named auth context for the model layout and CRUD pages", () => {
110
+ const model = createBasicModelInfo({ name: "Product", authPolicy: { policy: "adminOnly" } });
111
+ const options = createUIAuthOptions(model.authPolicy!, namedAuthConfig);
112
+ const layout = generateAuthLayout(model, options);
113
+ const page = generateNewPage(model, options);
114
+
115
+ expect(options.authContextImport).toBe("@/lib/auth/schemes/admin/auth-context");
116
+ expect(layout).toContain("AuthProvider } from '@/lib/auth/schemes/admin/auth-context'");
117
+ expect(page).toContain("useAuth } from '@/lib/auth/schemes/admin/auth-context'");
118
+ });
119
+ });
120
+
121
+ describe("add-auth named scheme CLI", () => {
122
+ const originalCwd = process.cwd();
123
+ let root: string;
124
+
125
+ beforeEach(() => {
126
+ root = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-multi-auth-"));
127
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
128
+ fs.writeFileSync(path.join(root, "swallowkit.config.json"), JSON.stringify({ backend: { language: "typescript" }, auth: { schemes: {}, authorization: { defaultPolicy: "anonymous", policies: {} } } }, null, 2));
129
+ process.chdir(root);
130
+ });
131
+
132
+ afterEach(() => {
133
+ process.chdir(originalCwd);
134
+ fs.rmSync(root, { recursive: true, force: true });
135
+ });
136
+
137
+ it("adds two schemes without overwriting the first verifier", async () => {
138
+ await addAuthCommand({ scheme: "admin", provider: "swa" });
139
+ await addAuthCommand({ scheme: "lineUser", provider: "external-token" });
140
+ const verifier = path.join(root, "functions", "src", "auth", "schemes", "line-user", "verifier.ts");
141
+ fs.writeFileSync(verifier, "// user verifier\n", "utf-8");
142
+ const config = JSON.parse(fs.readFileSync(path.join(root, "swallowkit.config.json"), "utf-8"));
143
+ expect(config.auth.schemes.admin.provider).toBe("swa");
144
+ expect(config.auth.schemes.lineUser.provider).toBe("external-token");
145
+ await expect(addAuthCommand({ scheme: "lineUser", provider: "external-token" })).rejects.toThrow("already exists");
146
+ expect(fs.readFileSync(verifier, "utf-8")).toBe("// user verifier\n");
147
+ expect(fs.readFileSync(path.join(root, "lib", "api", "call-function.ts"), "utf-8")).toContain("x-functions-key");
148
+ });
149
+
150
+ it("updates a JavaScript config that already has a trailing comma", async () => {
151
+ fs.rmSync(path.join(root, "swallowkit.config.json"));
152
+ fs.writeFileSync(path.join(root, "swallowkit.config.js"), "module.exports = {\n backend: { language: 'typescript' },\n api: { endpoint: '/api/test' },\n};\n");
153
+ await addAuthCommand({ scheme: "admin", provider: "swa" });
154
+ const content = fs.readFileSync(path.join(root, "swallowkit.config.js"), "utf-8");
155
+ expect(content).not.toContain(",,");
156
+ const configModule = { exports: {} as Record<string, unknown> };
157
+ new Function("module", content)(configModule);
158
+ expect((configModule.exports as { auth: { schemes: { admin: { provider: string } } } }).auth.schemes.admin.provider).toBe("swa");
159
+ });
160
+ });
@@ -0,0 +1,105 @@
1
+ import {
2
+ generateBFFCallFunction,
3
+ generateCompactBFFRoutes,
4
+ } from "../core/scaffold/nextjs-generator";
5
+ import { createBasicModelInfo } from "./fixtures";
6
+
7
+ describe("generateBFFCallFunction", () => {
8
+ it("generates call function helper code", () => {
9
+ const code = generateBFFCallFunction();
10
+ expect(code).toMatchSnapshot();
11
+ });
12
+
13
+ it("includes getFunctionsBaseUrl helper", () => {
14
+ const code = generateBFFCallFunction();
15
+ expect(code).toContain("getFunctionsBaseUrl");
16
+ expect(code).toContain("BACKEND_FUNCTIONS_BASE_URL");
17
+ });
18
+
19
+ it("includes callFunction export", () => {
20
+ const code = generateBFFCallFunction();
21
+ expect(code).toContain("export async function callFunction");
22
+ });
23
+
24
+ it("uses a server-side Functions key and fails closed outside localhost", () => {
25
+ const code = generateBFFCallFunction();
26
+ expect(code).toContain("BACKEND_FUNCTIONS_KEY");
27
+ expect(code).toContain("'x-functions-key'");
28
+ expect(code).toContain("required for non-local Functions calls");
29
+ expect(code).not.toContain("?code=");
30
+ });
31
+
32
+ it("includes z.ZodSchema typings", () => {
33
+ const code = generateBFFCallFunction();
34
+ expect(code).toContain("z.ZodSchema");
35
+ });
36
+ });
37
+
38
+ describe("generateCompactBFFRoutes", () => {
39
+ it("generates list and detail routes", () => {
40
+ const model = createBasicModelInfo();
41
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
42
+
43
+ expect(routes.listRoute).toMatchSnapshot();
44
+ expect(routes.detailRoute).toMatchSnapshot();
45
+ });
46
+
47
+ it("imports the correct schema", () => {
48
+ const model = createBasicModelInfo();
49
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
50
+
51
+ expect(routes.listRoute).toContain(
52
+ "import { todoSchema } from '@myapp/shared'"
53
+ );
54
+ expect(routes.detailRoute).toContain(
55
+ "import { todoSchema } from '@myapp/shared'"
56
+ );
57
+ });
58
+
59
+ it("uses correct API paths (camelCase)", () => {
60
+ const model = createBasicModelInfo();
61
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
62
+
63
+ expect(routes.listRoute).toContain("path: '/api/todo'");
64
+ expect(routes.detailRoute).toContain("/api/todo/");
65
+ });
66
+
67
+ it("list route has GET and POST handlers", () => {
68
+ const model = createBasicModelInfo();
69
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
70
+
71
+ expect(routes.listRoute).toContain("export async function GET()");
72
+ expect(routes.listRoute).toContain("export async function POST(");
73
+ });
74
+
75
+ it("detail route has GET, PUT, and DELETE handlers", () => {
76
+ const model = createBasicModelInfo();
77
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
78
+
79
+ expect(routes.detailRoute).toContain("export async function GET(");
80
+ expect(routes.detailRoute).toContain("export async function PUT(");
81
+ expect(routes.detailRoute).toContain("export async function DELETE(");
82
+ });
83
+
84
+ it("creates InputSchema that omits managed fields", () => {
85
+ const model = createBasicModelInfo();
86
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
87
+
88
+ expect(routes.listRoute).toContain(
89
+ "todoSchema.omit({ id: true, createdAt: true, updatedAt: true })"
90
+ );
91
+ });
92
+
93
+ it("handles multi-word model names", () => {
94
+ const model = createBasicModelInfo({
95
+ name: "BlogPost",
96
+ schemaName: "blogPostSchema",
97
+ });
98
+ const routes = generateCompactBFFRoutes(model, "@myapp/shared");
99
+
100
+ expect(routes.listRoute).toContain("path: '/api/blogPost'");
101
+ expect(routes.listRoute).toContain(
102
+ "import { blogPostSchema } from '@myapp/shared'"
103
+ );
104
+ });
105
+ });
@@ -0,0 +1,43 @@
1
+ import { generateOpenApiDocument } from "../core/scaffold/openapi-generator";
2
+ import { createBasicModelInfo } from "./fixtures";
3
+
4
+ describe("generateOpenApiDocument", () => {
5
+ it("emits an OpenAPI document for the root model", () => {
6
+ const model = createBasicModelInfo();
7
+ const document = JSON.parse(generateOpenApiDocument([model], model));
8
+
9
+ expect(document.openapi).toBe("3.0.3");
10
+ expect(document.components.schemas.Todo.properties.title.type).toBe("string");
11
+ expect(document.paths["/api/todo"].post.requestBody.content["application/json"].schema.$ref)
12
+ .toBe("#/components/schemas/Todo");
13
+ });
14
+
15
+ it("emits nested schema references when present", () => {
16
+ const todo = createBasicModelInfo({
17
+ fields: [
18
+ { name: "id", type: "string", isOptional: false, isArray: false },
19
+ { name: "title", type: "string", isOptional: false, isArray: false },
20
+ {
21
+ name: "category",
22
+ type: "object",
23
+ isOptional: false,
24
+ isArray: false,
25
+ isNestedSchema: true,
26
+ nestedModelName: "Category",
27
+ },
28
+ ],
29
+ });
30
+ const category = createBasicModelInfo({
31
+ name: "Category",
32
+ schemaName: "categorySchema",
33
+ fields: [
34
+ { name: "id", type: "string", isOptional: false, isArray: false },
35
+ { name: "name", type: "string", isOptional: false, isArray: false },
36
+ ],
37
+ });
38
+
39
+ const document = JSON.parse(generateOpenApiDocument([todo, category], todo));
40
+ expect(document.components.schemas.Todo.properties.category.$ref).toBe("#/components/schemas/Category");
41
+ expect(document.components.schemas.Category.properties.name.type).toBe("string");
42
+ });
43
+ });