swallowkit 1.0.0-beta.6 → 1.0.0-beta.60

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 +86 -177
  2. package/README.md +86 -178
  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 +163 -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 +19 -81
  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 +78 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +1453 -898
  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 +333 -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 +159 -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 +243 -43
  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 +5 -2
  192. package/dist/utils/package-manager.d.ts.map +1 -1
  193. package/dist/utils/package-manager.js +30 -9
  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 +9 -5
  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 +439 -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 +411 -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 +81 -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 +227 -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 +158 -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 +3541 -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 +350 -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 +502 -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 +854 -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 +246 -0
  295. package/src/utils/python-uv.ts +96 -0
  296. package/src/version.ts +13 -0
@@ -0,0 +1,81 @@
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
+ it("preserves array and enum metadata for schemas with defaults", async () => {
7
+ const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-model-parser-"));
8
+ const modelPath = path.join(tempDir, "product.ts");
9
+
10
+ try {
11
+ fs.writeFileSync(
12
+ modelPath,
13
+ `import { z } from 'zod/v4';
14
+
15
+ export const Product = z.object({
16
+ id: z.string(),
17
+ name: z.string().min(1),
18
+ price: z.number().min(0),
19
+ tags: z.array(z.string()).default([]),
20
+ status: z.enum(['draft', 'active', 'archived']).default('draft'),
21
+ createdAt: z.string().optional(),
22
+ updatedAt: z.string().optional(),
23
+ });
24
+
25
+ export type Product = z.infer<typeof Product>;
26
+ `,
27
+ "utf-8"
28
+ );
29
+
30
+ const model = await parseModelFile(modelPath);
31
+ expect(model.fields).toEqual(
32
+ expect.arrayContaining([
33
+ expect.objectContaining({
34
+ name: "tags",
35
+ type: "string",
36
+ isArray: true,
37
+ isOptional: true,
38
+ }),
39
+ expect.objectContaining({
40
+ name: "status",
41
+ type: "string",
42
+ isArray: false,
43
+ isOptional: true,
44
+ enumValues: ["draft", "active", "archived"],
45
+ }),
46
+ ])
47
+ );
48
+ } finally {
49
+ fs.rmSync(tempDir, { recursive: true, force: true });
50
+ }
51
+ });
52
+
53
+ it("distinguishes optional and nullable fields", async () => {
54
+ const tempDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-model-parser-"));
55
+ const modelPath = path.join(tempDir, "coupon.ts");
56
+
57
+ try {
58
+ fs.writeFileSync(
59
+ modelPath,
60
+ `import { z } from 'zod/v4';
61
+
62
+ export const couponSchema = z.object({
63
+ requiredUserId: z.string(),
64
+ optionalUserId: z.string().optional(),
65
+ nullableUserId: z.string().nullable(),
66
+ });
67
+ `,
68
+ "utf-8"
69
+ );
70
+
71
+ const model = await parseModelFile(modelPath);
72
+ expect(model.fields).toEqual(expect.arrayContaining([
73
+ expect.objectContaining({ name: "requiredUserId", isOptional: false, isNullable: false }),
74
+ expect.objectContaining({ name: "optionalUserId", isOptional: true, isNullable: false }),
75
+ expect.objectContaining({ name: "nullableUserId", isOptional: false, isNullable: true }),
76
+ ]));
77
+ } finally {
78
+ fs.rmSync(tempDir, { recursive: true, force: true });
79
+ }
80
+ });
81
+ });
@@ -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
+ });
@@ -0,0 +1,227 @@
1
+ import {
2
+ detectFromUserAgent,
3
+ getCommands,
4
+ getWorkspaceConfig,
5
+ getCiSetupStep,
6
+ getCiInstallCommand,
7
+ getAzurePipelinesSetup,
8
+ getBuildScript,
9
+ getSwaAppBuildCommand,
10
+ getFunctionsPrestart,
11
+ getFunctionsStartScript,
12
+ spawnArgs,
13
+ } from "../utils/package-manager";
14
+
15
+ describe("detectFromUserAgent", () => {
16
+ it("falls back to npm when pnpm is not installed", () => {
17
+ expect(detectFromUserAgent(() => false)).toBe("npm");
18
+ });
19
+
20
+ it("prefers pnpm when it is installed", () => {
21
+ expect(detectFromUserAgent(() => true)).toBe("pnpm");
22
+ });
23
+ });
24
+
25
+ describe("getCommands", () => {
26
+ describe("pnpm", () => {
27
+ const cmds = getCommands("pnpm");
28
+
29
+ it("returns correct name", () => {
30
+ expect(cmds.name).toBe("pnpm");
31
+ });
32
+
33
+ it("returns correct install command", () => {
34
+ expect(cmds.install).toBe("pnpm install");
35
+ });
36
+
37
+ it("returns correct ci command", () => {
38
+ expect(cmds.ci).toBe("pnpm install --frozen-lockfile");
39
+ });
40
+
41
+ it("returns correct add command", () => {
42
+ expect(cmds.add).toBe("pnpm add");
43
+ });
44
+
45
+ it("returns correct addDev command", () => {
46
+ expect(cmds.addDev).toBe("pnpm add -D");
47
+ });
48
+
49
+ it("returns correct exec command", () => {
50
+ expect(cmds.exec).toBe("pnpm exec");
51
+ });
52
+
53
+ it("returns correct dlx command", () => {
54
+ expect(cmds.dlx).toBe("pnpm dlx");
55
+ });
56
+
57
+ it("returns correct runFilter for workspace", () => {
58
+ expect(cmds.runFilter("shared")).toBe("pnpm run --filter shared");
59
+ });
60
+
61
+ it("returns --use-pnpm flag for create-next-app", () => {
62
+ expect(cmds.createNextAppFlag).toBe("--use-pnpm");
63
+ });
64
+ });
65
+
66
+ describe("npm", () => {
67
+ const cmds = getCommands("npm");
68
+
69
+ it("returns correct name", () => {
70
+ expect(cmds.name).toBe("npm");
71
+ });
72
+
73
+ it("returns correct install command", () => {
74
+ expect(cmds.install).toBe("npm install");
75
+ });
76
+
77
+ it("returns correct ci command", () => {
78
+ expect(cmds.ci).toBe("npm ci");
79
+ });
80
+
81
+ it("returns correct add command", () => {
82
+ expect(cmds.add).toBe("npm install");
83
+ });
84
+
85
+ it("returns correct addDev command", () => {
86
+ expect(cmds.addDev).toBe("npm install -D");
87
+ });
88
+
89
+ it("returns correct exec command", () => {
90
+ expect(cmds.exec).toBe("npx");
91
+ });
92
+
93
+ it("returns correct dlx command", () => {
94
+ expect(cmds.dlx).toBe("npx");
95
+ });
96
+
97
+ it("returns correct runFilter for workspace", () => {
98
+ expect(cmds.runFilter("shared")).toBe("npm run --workspace=shared");
99
+ });
100
+
101
+ it("returns null for create-next-app flag", () => {
102
+ expect(cmds.createNextAppFlag).toBeNull();
103
+ });
104
+ });
105
+ });
106
+
107
+ describe("getWorkspaceConfig", () => {
108
+ it("returns pnpm-workspace.yaml config for pnpm", () => {
109
+ const config = getWorkspaceConfig("pnpm", ["packages/*", "apps/*"]);
110
+ expect(config.type).toBe("file");
111
+ if (config.type === "file") {
112
+ expect(config.filename).toBe("pnpm-workspace.yaml");
113
+ expect(config.content).toContain("packages:");
114
+ expect(config.content).toContain(" - packages/*");
115
+ expect(config.content).toContain(" - apps/*");
116
+ }
117
+ });
118
+
119
+ it("returns packageJson config for npm", () => {
120
+ const config = getWorkspaceConfig("npm", ["packages/*", "apps/*"]);
121
+ expect(config.type).toBe("packageJson");
122
+ if (config.type === "packageJson") {
123
+ expect(config.field).toBe("workspaces");
124
+ expect(config.value).toEqual(["packages/*", "apps/*"]);
125
+ }
126
+ });
127
+ });
128
+
129
+ describe("getCiSetupStep", () => {
130
+ it("returns pnpm setup step for pnpm", () => {
131
+ const step = getCiSetupStep("pnpm");
132
+ expect(step).toContain("pnpm/action-setup");
133
+ expect(step).toContain("version: latest");
134
+ });
135
+
136
+ it("returns empty string for npm", () => {
137
+ expect(getCiSetupStep("npm")).toBe("");
138
+ });
139
+ });
140
+
141
+ describe("getSwaAppBuildCommand", () => {
142
+ it("returns a pnpm build command for pnpm projects", () => {
143
+ expect(getSwaAppBuildCommand("pnpm")).toBe("pnpm install --frozen-lockfile && pnpm run build");
144
+ });
145
+
146
+ it("returns an npm build command for npm projects", () => {
147
+ expect(getSwaAppBuildCommand("npm")).toBe("npm ci && npm run build");
148
+ });
149
+
150
+ it("uses npm ci for pnpm-initialized projects once an npm lockfile is generated", () => {
151
+ expect(getSwaAppBuildCommand("npm", "pnpm")).toBe("npm ci && npm run build");
152
+ });
153
+ });
154
+
155
+ describe("getCiInstallCommand", () => {
156
+ it("returns npm ci for npm projects", () => {
157
+ expect(getCiInstallCommand("npm", "npm")).toBe("npm ci");
158
+ });
159
+
160
+ it("returns npm ci for pnpm projects once an npm lockfile is generated", () => {
161
+ expect(getCiInstallCommand("npm", "pnpm")).toBe("npm ci");
162
+ });
163
+ });
164
+
165
+ describe("getAzurePipelinesSetup", () => {
166
+ it("returns corepack step for pnpm", () => {
167
+ const step = getAzurePipelinesSetup("pnpm");
168
+ expect(step).toContain("corepack enable");
169
+ expect(step).toContain("pnpm");
170
+ });
171
+
172
+ it("returns empty string for npm", () => {
173
+ expect(getAzurePipelinesSetup("npm")).toBe("");
174
+ });
175
+ });
176
+
177
+ describe("getBuildScript", () => {
178
+ it("uses pnpm run --filter for pnpm", () => {
179
+ expect(getBuildScript("pnpm")).toContain("pnpm run --filter shared build");
180
+ expect(getBuildScript("pnpm")).toContain("fs.cpSync");
181
+ });
182
+
183
+ it("uses npm run --workspace for npm", () => {
184
+ expect(getBuildScript("npm")).toContain("npm run --workspace=shared build");
185
+ expect(getBuildScript("npm")).toContain("fs.cpSync");
186
+ });
187
+ });
188
+
189
+ describe("getFunctionsPrestart", () => {
190
+ it("uses pnpm run build for pnpm", () => {
191
+ expect(getFunctionsPrestart("pnpm")).toBe("pnpm run build");
192
+ });
193
+
194
+ it("uses npm run build for npm", () => {
195
+ expect(getFunctionsPrestart("npm")).toBe("npm run build");
196
+ });
197
+ });
198
+
199
+ describe("getFunctionsStartScript", () => {
200
+ it("uses pnpm start for pnpm", () => {
201
+ expect(getFunctionsStartScript("pnpm")).toContain("pnpm start");
202
+ });
203
+
204
+ it("uses npm start for npm", () => {
205
+ expect(getFunctionsStartScript("npm")).toContain("npm start");
206
+ });
207
+
208
+ it("uses func start for csharp backends", () => {
209
+ expect(getFunctionsStartScript("pnpm", "csharp")).toBe("cd functions && func start");
210
+ });
211
+
212
+ it("uses func start for python backends", () => {
213
+ expect(getFunctionsStartScript("npm", "python")).toBe("cd functions && func start");
214
+ });
215
+ });
216
+
217
+ describe("spawnArgs", () => {
218
+ it("returns cmd and args for pnpm", () => {
219
+ const result = spawnArgs("pnpm", ["add", "next@latest"]);
220
+ expect(result).toEqual({ cmd: "pnpm", args: ["add", "next@latest"] });
221
+ });
222
+
223
+ it("returns cmd and args for npm", () => {
224
+ const result = spawnArgs("npm", ["install", "next@latest"]);
225
+ expect(result).toEqual({ cmd: "npm", args: ["install", "next@latest"] });
226
+ });
227
+ });