swallowkit 1.0.0-beta.8 → 1.0.0

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 (272) hide show
  1. package/README.ja.md +87 -181
  2. package/README.md +87 -238
  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 +27 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -1
  25. package/dist/cli/commands/dev-seeds.js +188 -6
  26. package/dist/cli/commands/dev-seeds.js.map +1 -1
  27. package/dist/cli/commands/dev.d.ts +30 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +495 -129
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/init.d.ts +70 -0
  32. package/dist/cli/commands/init.d.ts.map +1 -1
  33. package/dist/cli/commands/init.js +797 -768
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/scaffold.d.ts +24 -3
  36. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  37. package/dist/cli/commands/scaffold.js +408 -189
  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 +335 -58
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/core/config.d.ts +13 -3
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +151 -8
  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/auth-operations.d.ts +61 -0
  56. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  57. package/dist/core/operations/auth-operations.js +272 -0
  58. package/dist/core/operations/auth-operations.js.map +1 -0
  59. package/dist/core/operations/create-model.d.ts +15 -0
  60. package/dist/core/operations/create-model.d.ts.map +1 -0
  61. package/dist/core/operations/create-model.js +185 -0
  62. package/dist/core/operations/create-model.js.map +1 -0
  63. package/dist/core/operations/file-session.d.ts +56 -0
  64. package/dist/core/operations/file-session.d.ts.map +1 -0
  65. package/dist/core/operations/file-session.js +203 -0
  66. package/dist/core/operations/file-session.js.map +1 -0
  67. package/dist/core/operations/provision-operations.d.ts +52 -0
  68. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  69. package/dist/core/operations/provision-operations.js +211 -0
  70. package/dist/core/operations/provision-operations.js.map +1 -0
  71. package/dist/core/operations/runtime.d.ts +37 -0
  72. package/dist/core/operations/runtime.d.ts.map +1 -0
  73. package/dist/core/operations/runtime.js +248 -0
  74. package/dist/core/operations/runtime.js.map +1 -0
  75. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  76. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  77. package/dist/core/operations/scaffold-apply.js +57 -0
  78. package/dist/core/operations/scaffold-apply.js.map +1 -0
  79. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  80. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-machine.js +51 -0
  82. package/dist/core/operations/scaffold-machine.js.map +1 -0
  83. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  84. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-plan.js +187 -0
  86. package/dist/core/operations/scaffold-plan.js.map +1 -0
  87. package/dist/core/project/agent-instructions.d.ts +29 -0
  88. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  89. package/dist/core/project/agent-instructions.js +160 -0
  90. package/dist/core/project/agent-instructions.js.map +1 -0
  91. package/dist/core/project/artifacts.d.ts +58 -0
  92. package/dist/core/project/artifacts.d.ts.map +1 -0
  93. package/dist/core/project/artifacts.js +179 -0
  94. package/dist/core/project/artifacts.js.map +1 -0
  95. package/dist/core/project/boundaries.d.ts +50 -0
  96. package/dist/core/project/boundaries.d.ts.map +1 -0
  97. package/dist/core/project/boundaries.js +117 -0
  98. package/dist/core/project/boundaries.js.map +1 -0
  99. package/dist/core/project/capabilities.d.ts +58 -0
  100. package/dist/core/project/capabilities.d.ts.map +1 -0
  101. package/dist/core/project/capabilities.js +135 -0
  102. package/dist/core/project/capabilities.js.map +1 -0
  103. package/dist/core/project/drift.d.ts +29 -0
  104. package/dist/core/project/drift.d.ts.map +1 -0
  105. package/dist/core/project/drift.js +188 -0
  106. package/dist/core/project/drift.js.map +1 -0
  107. package/dist/core/project/infra.d.ts +49 -0
  108. package/dist/core/project/infra.d.ts.map +1 -0
  109. package/dist/core/project/infra.js +167 -0
  110. package/dist/core/project/infra.js.map +1 -0
  111. package/dist/core/project/manifest.d.ts +92 -0
  112. package/dist/core/project/manifest.d.ts.map +1 -0
  113. package/dist/core/project/manifest.js +321 -0
  114. package/dist/core/project/manifest.js.map +1 -0
  115. package/dist/core/project/state.d.ts +15 -0
  116. package/dist/core/project/state.d.ts.map +1 -0
  117. package/dist/core/project/state.js +106 -0
  118. package/dist/core/project/state.js.map +1 -0
  119. package/dist/core/project/validation.d.ts +20 -0
  120. package/dist/core/project/validation.d.ts.map +1 -0
  121. package/dist/core/project/validation.js +274 -0
  122. package/dist/core/project/validation.js.map +1 -0
  123. package/dist/core/project/workflows.d.ts +43 -0
  124. package/dist/core/project/workflows.d.ts.map +1 -0
  125. package/dist/core/project/workflows.js +299 -0
  126. package/dist/core/project/workflows.js.map +1 -0
  127. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  128. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  129. package/dist/core/scaffold/auth-generator.js +1774 -0
  130. package/dist/core/scaffold/auth-generator.js.map +1 -0
  131. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  132. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  134. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  135. package/dist/core/scaffold/functions-generator.d.ts +4 -3
  136. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  137. package/dist/core/scaffold/functions-generator.js +405 -134
  138. package/dist/core/scaffold/functions-generator.js.map +1 -1
  139. package/dist/core/scaffold/model-parser.d.ts +20 -0
  140. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  141. package/dist/core/scaffold/model-parser.js +229 -78
  142. package/dist/core/scaffold/model-parser.js.map +1 -1
  143. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  144. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  145. package/dist/core/scaffold/native-schema-generator.js +753 -0
  146. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  147. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  148. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  149. package/dist/core/scaffold/nextjs-generator.js +152 -8
  150. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  151. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  152. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/ui-generator.js +174 -34
  154. package/dist/core/scaffold/ui-generator.js.map +1 -1
  155. package/dist/core/verify/index.d.ts +69 -0
  156. package/dist/core/verify/index.d.ts.map +1 -0
  157. package/dist/core/verify/index.js +412 -0
  158. package/dist/core/verify/index.js.map +1 -0
  159. package/dist/machine/contracts.d.ts +24 -0
  160. package/dist/machine/contracts.d.ts.map +1 -0
  161. package/dist/machine/contracts.js +3 -0
  162. package/dist/machine/contracts.js.map +1 -0
  163. package/dist/machine/errors.d.ts +14 -0
  164. package/dist/machine/errors.d.ts.map +1 -0
  165. package/dist/machine/errors.js +42 -0
  166. package/dist/machine/errors.js.map +1 -0
  167. package/dist/machine/index.d.ts +3 -0
  168. package/dist/machine/index.d.ts.map +1 -0
  169. package/dist/machine/index.js +566 -0
  170. package/dist/machine/index.js.map +1 -0
  171. package/dist/mcp/index.d.ts +25 -0
  172. package/dist/mcp/index.d.ts.map +1 -0
  173. package/dist/mcp/index.js +425 -0
  174. package/dist/mcp/index.js.map +1 -0
  175. package/dist/types/index.d.ts +105 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/package-manager.d.ts +7 -11
  178. package/dist/utils/package-manager.d.ts.map +1 -1
  179. package/dist/utils/package-manager.js +42 -17
  180. package/dist/utils/package-manager.js.map +1 -1
  181. package/dist/utils/python-uv.d.ts +21 -0
  182. package/dist/utils/python-uv.d.ts.map +1 -0
  183. package/dist/utils/python-uv.js +111 -0
  184. package/dist/utils/python-uv.js.map +1 -0
  185. package/dist/version.d.ts +2 -0
  186. package/dist/version.d.ts.map +1 -0
  187. package/dist/version.js +49 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +15 -17
  190. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +716 -22
  191. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +19 -7
  192. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +20 -12
  193. package/src/__tests__/agent-instructions.test.ts +237 -0
  194. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  195. package/src/__tests__/auth.test.ts +770 -0
  196. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  197. package/src/__tests__/config.test.ts +176 -3
  198. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  199. package/src/__tests__/connector-mock-server.test.ts +459 -0
  200. package/src/__tests__/connector-model-bff.test.ts +162 -0
  201. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  202. package/src/__tests__/dev-seeds.test.ts +124 -0
  203. package/src/__tests__/dev.test.ts +254 -0
  204. package/src/__tests__/file-session.test.ts +98 -0
  205. package/src/__tests__/fixtures.ts +101 -0
  206. package/src/__tests__/functions-generator.test.ts +137 -1
  207. package/src/__tests__/init.test.ts +386 -1
  208. package/src/__tests__/jest-globals.d.ts +8 -0
  209. package/src/__tests__/machine.test.ts +371 -0
  210. package/src/__tests__/mcp.test.ts +253 -0
  211. package/src/__tests__/model-parser.test.ts +142 -0
  212. package/src/__tests__/multi-auth.test.ts +160 -0
  213. package/src/__tests__/nextjs-generator.test.ts +8 -0
  214. package/src/__tests__/package-manager.test.ts +59 -4
  215. package/src/__tests__/plan-apply.test.ts +499 -0
  216. package/src/__tests__/python-uv.test.ts +48 -0
  217. package/src/__tests__/scaffold.test.ts +287 -24
  218. package/src/__tests__/tsconfig.json +15 -0
  219. package/src/__tests__/ui-generator.test.ts +104 -0
  220. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  221. package/src/cli/commands/add-auth.ts +853 -0
  222. package/src/cli/commands/add-connector.ts +159 -0
  223. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  224. package/src/cli/commands/create-model.ts +21 -100
  225. package/src/cli/commands/dev-seeds.ts +278 -7
  226. package/src/cli/commands/dev.ts +607 -160
  227. package/src/cli/commands/init.ts +880 -808
  228. package/src/cli/commands/scaffold.ts +531 -236
  229. package/src/cli/index.ts +338 -60
  230. package/src/core/config.ts +145 -9
  231. package/src/core/mock/connector-mock-server.ts +555 -0
  232. package/src/core/mock/zod-mock-generator.ts +205 -0
  233. package/src/core/operations/auth-operations.ts +360 -0
  234. package/src/core/operations/create-model.ts +191 -0
  235. package/src/core/operations/file-session.ts +222 -0
  236. package/src/core/operations/provision-operations.ts +297 -0
  237. package/src/core/operations/runtime.ts +258 -0
  238. package/src/core/operations/scaffold-apply.ts +106 -0
  239. package/src/core/operations/scaffold-machine.ts +77 -0
  240. package/src/core/operations/scaffold-plan.ts +215 -0
  241. package/src/core/project/agent-instructions.ts +167 -0
  242. package/src/core/project/agent-templates/AGENTS.md +504 -0
  243. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  244. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  245. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  246. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  247. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  248. package/src/core/project/artifacts.ts +205 -0
  249. package/src/core/project/boundaries.ts +170 -0
  250. package/src/core/project/capabilities.ts +193 -0
  251. package/src/core/project/drift.ts +202 -0
  252. package/src/core/project/infra.ts +196 -0
  253. package/src/core/project/manifest.ts +402 -0
  254. package/src/core/project/state.ts +77 -0
  255. package/src/core/project/validation.ts +295 -0
  256. package/src/core/project/workflows.ts +295 -0
  257. package/src/core/scaffold/auth-generator.ts +1824 -0
  258. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  259. package/src/core/scaffold/functions-generator.ts +432 -134
  260. package/src/core/scaffold/model-parser.ts +244 -90
  261. package/src/core/scaffold/native-schema-generator.ts +904 -0
  262. package/src/core/scaffold/nextjs-generator.ts +174 -9
  263. package/src/core/scaffold/ui-generator.ts +191 -36
  264. package/src/core/verify/index.ts +468 -0
  265. package/src/machine/contracts.ts +33 -0
  266. package/src/machine/errors.ts +45 -0
  267. package/src/machine/index.ts +690 -0
  268. package/src/mcp/index.ts +423 -0
  269. package/src/types/index.ts +137 -0
  270. package/src/utils/package-manager.ts +41 -19
  271. package/src/utils/python-uv.ts +96 -0
  272. package/src/version.ts +13 -0
@@ -0,0 +1,904 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { spawn, spawnSync } from "child_process";
5
+ import { BackendLanguage } from "../../types";
6
+ import { FileOperationSession } from "../operations/file-session";
7
+ import { ModelInfo, toKebabCase } from "./model-parser";
8
+ import { generateOpenApiDocument } from "./openapi-generator";
9
+ import {
10
+ buildProjectLocalUvEnv,
11
+ buildProjectLocalUvInstallerEnv,
12
+ buildUvPipInstallArgs,
13
+ buildUvVenvArgs,
14
+ getProjectLocalUvInstallerCommand,
15
+ getProjectLocalUvPaths,
16
+ getPythonProjectRoot,
17
+ } from "../../utils/python-uv";
18
+
19
+ export const NSWAG_CONSOLECORE_VERSION = "14.7.1";
20
+ export const PYTHON_SCHEMA_CODEGEN_REQUIREMENT = "datamodel-code-generator>=0.44.0,<1.0.0";
21
+ const PYTHON_OUTPUT_MODEL_TYPE = "pydantic_v2.BaseModel";
22
+
23
+ function getMachineAwareStdio(): "inherit" | "pipe" {
24
+ return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
25
+ }
26
+
27
+ function canRun(command: string, args: string[], cwd: string, env?: NodeJS.ProcessEnv): boolean {
28
+ const result = spawnSync(command, args, {
29
+ cwd,
30
+ env,
31
+ stdio: "ignore",
32
+ });
33
+
34
+ return !result.error && result.status === 0;
35
+ }
36
+
37
+ async function runCommand(
38
+ command: string,
39
+ args: string[],
40
+ cwd: string,
41
+ errorMessage: string,
42
+ env?: NodeJS.ProcessEnv
43
+ ): Promise<void> {
44
+ await new Promise<void>((resolve, reject) => {
45
+ const child = spawn(command, args, {
46
+ cwd,
47
+ env,
48
+ stdio: getMachineAwareStdio(),
49
+ });
50
+
51
+ child.on("close", (code) => {
52
+ if (code === 0) {
53
+ resolve();
54
+ return;
55
+ }
56
+
57
+ reject(new Error(`${errorMessage} (${command} ${args.join(" ")}) exited with code ${code}`));
58
+ });
59
+
60
+ child.on("error", (error) => reject(new Error(`${errorMessage}: ${error.message}`)));
61
+ });
62
+ }
63
+
64
+ export function buildCSharpCodegenToolManifestSource(): string {
65
+ return `${JSON.stringify(
66
+ {
67
+ version: 1,
68
+ isRoot: true,
69
+ tools: {
70
+ "nswag.consolecore": {
71
+ version: NSWAG_CONSOLECORE_VERSION,
72
+ commands: ["nswag"],
73
+ },
74
+ },
75
+ },
76
+ null,
77
+ 2
78
+ )}\n`;
79
+ }
80
+
81
+ export function buildPythonCodegenRequirementsSource(): string {
82
+ return `${PYTHON_SCHEMA_CODEGEN_REQUIREMENT}\n`;
83
+ }
84
+
85
+ function toSnakeCase(value: string): string {
86
+ return value
87
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
88
+ .replace(/[-\s]+/g, "_")
89
+ .toLowerCase();
90
+ }
91
+
92
+ function toPascalIdentifier(value: string): string {
93
+ if (value.includes("-") || value.includes("_")) {
94
+ return value
95
+ .split(/[-_]/)
96
+ .filter(Boolean)
97
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
98
+ .join("");
99
+ }
100
+
101
+ return value.charAt(0).toUpperCase() + value.slice(1);
102
+ }
103
+
104
+ function isDateLikeField(field: Pick<ModelInfo["fields"][number], "name" | "type">): boolean {
105
+ return field.type === "date" || (field.type === "string" && field.name.toLowerCase().endsWith("at"));
106
+ }
107
+
108
+ export function getCSharpSchemaModelPath(outputDir: string, modelName: string): string {
109
+ return path.join(outputDir, "src", "SwallowKitBackendModels", "Model", `${modelName}.cs`);
110
+ }
111
+
112
+ export function getCSharpSchemaOptionPath(outputDir: string): string {
113
+ return path.join(outputDir, "src", "SwallowKitBackendModels", "Client", "Option.cs");
114
+ }
115
+
116
+ export function getPythonSchemaModelPath(outputDir: string, modelName: string): string {
117
+ return path.join(outputDir, "backend_models", "models", `${toSnakeCase(modelName)}.py`);
118
+ }
119
+
120
+ export function getCSharpNativeGeneratorArgs(specPath: string, outputPath: string): string[] {
121
+ return [
122
+ "tool",
123
+ "run",
124
+ "nswag",
125
+ "openapi2csclient",
126
+ `/input:${specPath}`,
127
+ `/output:${outputPath}`,
128
+ "/namespace:SwallowKitBackendModels",
129
+ "/GenerateClientClasses:false",
130
+ "/GenerateClientInterfaces:false",
131
+ "/GenerateResponseClasses:false",
132
+ "/GenerateExceptionClasses:false",
133
+ "/GenerateDtoTypes:true",
134
+ "/GenerateNullableReferenceTypes:true",
135
+ "/GenerateOptionalPropertiesAsNullable:true",
136
+ "/JsonLibrary:SystemTextJson",
137
+ ];
138
+ }
139
+
140
+ export function getPythonNativeGeneratorArgs(specPath: string, outputPath: string): string[] {
141
+ return [
142
+ "-m",
143
+ "datamodel_code_generator",
144
+ "--input",
145
+ specPath,
146
+ "--input-file-type",
147
+ "openapi",
148
+ "--output",
149
+ outputPath,
150
+ "--output-model-type",
151
+ PYTHON_OUTPUT_MODEL_TYPE,
152
+ "--target-python-version",
153
+ "3.11",
154
+ "--disable-timestamp",
155
+ "--use-union-operator",
156
+ "--collapse-root-models",
157
+ ];
158
+ }
159
+
160
+ function getCSharpFieldBaseType(field: ModelInfo["fields"][number]): string {
161
+ if (field.isNestedSchema && field.nestedModelName) {
162
+ return field.nestedModelName;
163
+ }
164
+
165
+ if (field.enumValues?.length) {
166
+ return `${toPascalIdentifier(field.name)}Enum`;
167
+ }
168
+
169
+ if (field.isArray) {
170
+ return `List<${getCSharpArrayElementType(field)}>`;
171
+ }
172
+
173
+ switch (field.type) {
174
+ case "string":
175
+ return isDateLikeField(field) ? "DateTime" : "string";
176
+ case "number":
177
+ return "decimal";
178
+ case "boolean":
179
+ return "bool";
180
+ case "date":
181
+ return "DateTime";
182
+ case "object":
183
+ return "Dictionary<string, object>";
184
+ default:
185
+ return "object";
186
+ }
187
+ }
188
+
189
+ function getCSharpArrayElementType(field: ModelInfo["fields"][number]): string {
190
+ if (field.isNestedSchema && field.nestedModelName) {
191
+ return field.nestedModelName;
192
+ }
193
+
194
+ if (field.enumValues?.length) {
195
+ return `${toPascalIdentifier(field.name)}Enum`;
196
+ }
197
+
198
+ switch (field.type) {
199
+ case "string":
200
+ return isDateLikeField(field) ? "DateTime" : "string";
201
+ case "number":
202
+ return "decimal";
203
+ case "boolean":
204
+ return "bool";
205
+ case "date":
206
+ return "DateTime";
207
+ case "object":
208
+ return "Dictionary<string, object>";
209
+ default:
210
+ return "object";
211
+ }
212
+ }
213
+
214
+ function getCSharpPropertyType(field: ModelInfo["fields"][number]): string {
215
+ const baseType = getCSharpFieldBaseType(field);
216
+ return field.isOptional ? `${baseType}?` : baseType;
217
+ }
218
+
219
+ function getCSharpOptionType(field: ModelInfo["fields"][number]): string {
220
+ return `Option<${getCSharpPropertyType(field)}>`;
221
+ }
222
+
223
+ function generateLegacyCompatibleOptionSource(): string {
224
+ return `// <auto-generated>
225
+ // Minimal Option<T> for OpenAPI Generator model compatibility.
226
+ // Full client supporting files are excluded to avoid Polly version conflicts.
227
+ // </auto-generated>
228
+
229
+ #nullable enable
230
+
231
+ namespace SwallowKitBackendModels.Client
232
+ {
233
+ /// <summary>
234
+ /// A wrapper for nullable/optional properties generated by OpenAPI Generator.
235
+ /// Tracks whether a value has been explicitly set (distinguishing null from absent).
236
+ /// </summary>
237
+ public readonly struct Option<TValue>
238
+ {
239
+ /// <summary>Whether this option has been explicitly set.</summary>
240
+ public bool IsSet { get; }
241
+
242
+ /// <summary>The contained value (may be default if not set).</summary>
243
+ public TValue Value { get; }
244
+
245
+ /// <summary>Create an Option with an explicit value.</summary>
246
+ public Option(TValue value)
247
+ {
248
+ IsSet = true;
249
+ Value = value;
250
+ }
251
+
252
+ /// <summary>Implicit conversion from Option to its inner value.</summary>
253
+ public static implicit operator TValue(Option<TValue> option) => option.Value;
254
+ }
255
+ }
256
+ `;
257
+ }
258
+
259
+ function buildCSharpEnumMembers(values: string[]): string {
260
+ return values
261
+ .map((value, index) => ` ${toPascalIdentifier(value)} = ${index + 1}`)
262
+ .join(",\n\n");
263
+ }
264
+
265
+ function buildCSharpEnumFromStringCases(field: ModelInfo["fields"][number], nullable: boolean): string {
266
+ const enumType = `${toPascalIdentifier(field.name)}Enum`;
267
+ return field.enumValues!
268
+ .map((value) => ` if (value.Equals("${value}", StringComparison.Ordinal))\n return ${enumType}.${toPascalIdentifier(value)};`)
269
+ .join("\n\n") + (nullable ? `\n\n return null;` : `\n\n throw new NotImplementedException($"Could not convert value to type ${enumType}: '{value}'");`);
270
+ }
271
+
272
+ function buildCSharpEnumToJsonCases(field: ModelInfo["fields"][number]): string {
273
+ const enumType = `${toPascalIdentifier(field.name)}Enum`;
274
+ return field.enumValues!
275
+ .map((value) => ` if (value == ${enumType}.${toPascalIdentifier(value)})\n return "${value}";`)
276
+ .join("\n\n");
277
+ }
278
+
279
+ function generateLegacyCompatibleCSharpModelSource(model: ModelInfo): string {
280
+ const requiredFields = model.fields.filter((field) => !field.isOptional);
281
+ const optionalFields = model.fields.filter((field) => field.isOptional);
282
+
283
+ const constructorParams = [
284
+ ...requiredFields.map((field) => `${getCSharpFieldBaseType(field)} ${field.name}`),
285
+ ...optionalFields.map((field) => `${getCSharpOptionType(field)} ${field.name} = default`),
286
+ ].join(", ");
287
+
288
+ const constructorAssignments = [
289
+ ...requiredFields.map((field) => ` ${toPascalIdentifier(field.name)} = ${field.name};`),
290
+ ...optionalFields.map((field) => ` ${toPascalIdentifier(field.name)}Option = ${field.name};`),
291
+ " OnCreated();",
292
+ ].join("\n");
293
+
294
+ const enumBlocks = model.fields
295
+ .filter((field) => field.enumValues?.length)
296
+ .map((field) => {
297
+ const enumType = `${toPascalIdentifier(field.name)}Enum`;
298
+ return ` /// <summary>
299
+ /// Defines ${toPascalIdentifier(field.name)}
300
+ /// </summary>
301
+ [JsonConverter(typeof(JsonStringEnumConverter))]
302
+ public enum ${enumType}
303
+ {
304
+ ${buildCSharpEnumMembers(field.enumValues!)}
305
+ }
306
+
307
+ public static ${enumType} ${enumType}FromString(string value)
308
+ {
309
+ ${buildCSharpEnumFromStringCases(field, false)}
310
+ }
311
+
312
+ public static ${enumType}? ${enumType}FromStringOrDefault(string value)
313
+ {
314
+ ${buildCSharpEnumFromStringCases(field, true)}
315
+ }
316
+
317
+ public static string ${enumType}ToJsonValue(${enumType}? value)
318
+ {
319
+ ${buildCSharpEnumToJsonCases(field)}
320
+
321
+ throw new NotImplementedException($"Value could not be handled: '{value}'");
322
+ }`;
323
+ })
324
+ .join("\n\n");
325
+
326
+ const propertyBlocks = model.fields
327
+ .map((field) => {
328
+ const propertyName = toPascalIdentifier(field.name);
329
+ const propertyType = getCSharpPropertyType(field);
330
+
331
+ if (!field.isOptional) {
332
+ return ` /// <summary>
333
+ /// Gets or Sets ${propertyName}
334
+ /// </summary>
335
+ [JsonPropertyName("${field.name}")]
336
+ public ${propertyType} ${propertyName} { get; set; }`;
337
+ }
338
+
339
+ return ` /// <summary>
340
+ /// Used to track the state of ${propertyName}
341
+ /// </summary>
342
+ [JsonIgnore]
343
+ [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
344
+ public ${getCSharpOptionType(field)} ${propertyName}Option { get; private set; }
345
+
346
+ /// <summary>
347
+ /// Gets or Sets ${propertyName}
348
+ /// </summary>
349
+ [JsonPropertyName("${field.name}")]
350
+ public ${propertyType} ${propertyName} { get { return this.${propertyName}Option; } set { this.${propertyName}Option = new(value); } }`;
351
+ })
352
+ .join("\n\n");
353
+
354
+ const toStringBody = model.fields
355
+ .map((field) => ` sb.Append(" ${toPascalIdentifier(field.name)}: ").Append(${toPascalIdentifier(field.name)}).Append("\\n");`)
356
+ .join("\n");
357
+
358
+ return `// <auto-generated>
359
+ /*
360
+ * ${model.name} API
361
+ *
362
+ * Generated from SwallowKit Zod model metadata.
363
+ *
364
+ * The version of the OpenAPI document: 1.0.0
365
+ * Generated by native SwallowKit schema compatibility layer
366
+ */
367
+
368
+ #nullable enable
369
+
370
+ using System;
371
+ using System.Collections.Generic;
372
+ using System.ComponentModel.DataAnnotations;
373
+ using System.Text;
374
+ using System.Text.Json.Serialization;
375
+ using SwallowKitBackendModels.Client;
376
+
377
+ namespace SwallowKitBackendModels.Model
378
+ {
379
+ /// <summary>
380
+ /// ${model.name}
381
+ /// </summary>
382
+ public partial class ${model.name} : IValidatableObject
383
+ {
384
+ [JsonConstructor]
385
+ public ${model.name}(${constructorParams})
386
+ {
387
+ ${constructorAssignments}
388
+ }
389
+
390
+ partial void OnCreated();
391
+ ${enumBlocks ? `\n\n${enumBlocks}` : ""}
392
+
393
+ ${propertyBlocks}
394
+
395
+ public override string ToString()
396
+ {
397
+ var sb = new StringBuilder();
398
+ sb.Append("class ${model.name} {\\n");
399
+ ${toStringBody}
400
+ sb.Append("}\\n");
401
+ return sb.ToString();
402
+ }
403
+
404
+ IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
405
+ {
406
+ yield break;
407
+ }
408
+ }
409
+ }
410
+ `;
411
+ }
412
+
413
+ function getPythonTypeName(field: ModelInfo["fields"][number]): string {
414
+ if (field.isNestedSchema && field.nestedModelName) {
415
+ return field.nestedModelName;
416
+ }
417
+
418
+ if (field.isArray) {
419
+ return `List[${getPythonArrayElementType(field)}]`;
420
+ }
421
+
422
+ switch (field.type) {
423
+ case "string":
424
+ return isDateLikeField(field) ? "datetime" : "StrictStr";
425
+ case "number":
426
+ return "Union[StrictFloat, StrictInt]";
427
+ case "boolean":
428
+ return "StrictBool";
429
+ case "date":
430
+ return "datetime";
431
+ case "object":
432
+ return "Dict[str, Any]";
433
+ default:
434
+ return "Any";
435
+ }
436
+ }
437
+
438
+ function getPythonArrayElementType(field: ModelInfo["fields"][number]): string {
439
+ if (field.isNestedSchema && field.nestedModelName) {
440
+ return field.nestedModelName;
441
+ }
442
+
443
+ switch (field.type) {
444
+ case "string":
445
+ return isDateLikeField(field) ? "datetime" : "StrictStr";
446
+ case "number":
447
+ return "Union[StrictFloat, StrictInt]";
448
+ case "boolean":
449
+ return "StrictBool";
450
+ case "date":
451
+ return "datetime";
452
+ case "object":
453
+ return "Dict[str, Any]";
454
+ default:
455
+ return "Any";
456
+ }
457
+ }
458
+
459
+ function buildPythonFieldDeclaration(field: ModelInfo["fields"][number]): string {
460
+ const pythonName = toSnakeCase(field.name);
461
+ const typeName = field.isOptional ? `Optional[${getPythonTypeName(field)}]` : getPythonTypeName(field);
462
+ const aliasSuffix = pythonName !== field.name ? `, alias="${field.name}"` : "";
463
+
464
+ if (field.isOptional) {
465
+ return `${pythonName}: ${typeName} = Field(default=None${aliasSuffix})`;
466
+ }
467
+
468
+ if (aliasSuffix) {
469
+ return `${pythonName}: ${typeName} = Field(${aliasSuffix.slice(2)})`;
470
+ }
471
+
472
+ return `${pythonName}: ${typeName}`;
473
+ }
474
+
475
+ function buildPythonEnumValidators(model: ModelInfo): string {
476
+ return model.fields
477
+ .filter((field) => field.enumValues?.length)
478
+ .map((field) => {
479
+ const pythonName = toSnakeCase(field.name);
480
+ const enumSet = field.enumValues!.map((value) => `'${value}'`).join(", ");
481
+ return ` @field_validator('${pythonName}')
482
+ def ${pythonName}_validate_enum(cls, value):
483
+ """Validates the enum"""
484
+ if value is None:
485
+ return value
486
+
487
+ if value not in set([${enumSet}]):
488
+ raise ValueError("must be one of enum values (${field.enumValues!.map((value) => `'${value}'`).join(", ")})")
489
+ return value`;
490
+ })
491
+ .join("\n\n");
492
+ }
493
+
494
+ function buildPythonModelImports(model: ModelInfo): string {
495
+ const nestedImports = Array.from(
496
+ new Set(
497
+ model.fields
498
+ .filter((field) => field.isNestedSchema && field.nestedModelName)
499
+ .map((field) => field.nestedModelName!)
500
+ )
501
+ )
502
+ .map((modelName) => `from .${toSnakeCase(modelName)} import ${modelName}`)
503
+ .join("\n");
504
+
505
+ return nestedImports ? `${nestedImports}\n\n` : "";
506
+ }
507
+
508
+ function generateLegacyCompatiblePythonModelSource(model: ModelInfo): string {
509
+ const fieldDeclarations = model.fields.map((field) => ` ${buildPythonFieldDeclaration(field)}`).join("\n");
510
+ const propertyNames = model.fields.map((field) => `"${field.name}"`).join(", ");
511
+ const validators = buildPythonEnumValidators(model);
512
+ const dictAssignments = model.fields
513
+ .map((field) => ` "${field.name}": obj.get("${field.name}")`)
514
+ .join(",\n");
515
+
516
+ return `# coding: utf-8
517
+
518
+ """
519
+ ${model.name} API
520
+
521
+ Generated from SwallowKit Zod model metadata.
522
+
523
+ The version of the OpenAPI document: 1.0.0
524
+ Generated by native SwallowKit schema compatibility layer
525
+
526
+ Do not edit the class manually.
527
+ """ # noqa: E501
528
+
529
+
530
+ from __future__ import annotations
531
+ import pprint
532
+ import re # noqa: F401
533
+ import json
534
+
535
+ from datetime import datetime
536
+ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, field_validator
537
+ from typing import Any, ClassVar, Dict, List, Optional, Set, Union
538
+ from typing_extensions import Self
539
+
540
+ ${buildPythonModelImports(model)}class ${model.name}(BaseModel):
541
+ """
542
+ ${model.name}
543
+ """ # noqa: E501
544
+ ${fieldDeclarations}
545
+ __properties: ClassVar[List[str]] = [${propertyNames}]
546
+ ${validators ? `\n\n${validators}` : ""}
547
+
548
+ model_config = ConfigDict(
549
+ populate_by_name=True,
550
+ validate_assignment=True,
551
+ protected_namespaces=(),
552
+ )
553
+
554
+
555
+ def to_str(self) -> str:
556
+ """Returns the string representation of the model using alias"""
557
+ return pprint.pformat(self.model_dump(by_alias=True))
558
+
559
+ def to_json(self) -> str:
560
+ """Returns the JSON representation of the model using alias"""
561
+ return json.dumps(self.to_dict())
562
+
563
+ @classmethod
564
+ def from_json(cls, json_str: str) -> Optional[Self]:
565
+ """Create an instance of ${model.name} from a JSON string"""
566
+ return cls.from_dict(json.loads(json_str))
567
+
568
+ def to_dict(self) -> Dict[str, Any]:
569
+ """Return the dictionary representation of the model using alias."""
570
+ excluded_fields: Set[str] = set([
571
+ ])
572
+
573
+ _dict = self.model_dump(
574
+ by_alias=True,
575
+ exclude=excluded_fields,
576
+ exclude_none=True,
577
+ )
578
+ return _dict
579
+
580
+ @classmethod
581
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
582
+ """Create an instance of ${model.name} from a dict"""
583
+ if obj is None:
584
+ return None
585
+
586
+ if not isinstance(obj, dict):
587
+ return cls.model_validate(obj)
588
+
589
+ _obj = cls.model_validate({
590
+ ${dictAssignments}
591
+ })
592
+ return _obj
593
+
594
+
595
+ `;
596
+ }
597
+
598
+ function buildGeneratedPythonPackageInitSource(models: ModelInfo[]): string {
599
+ return models.map((model) => `from .models.${toSnakeCase(model.name)} import ${model.name}`).join("\n") + "\n";
600
+ }
601
+
602
+ function buildGeneratedPythonModelsInitSource(models: ModelInfo[]): string {
603
+ return models.map((model) => `from .${toSnakeCase(model.name)} import ${model.name}`).join("\n") + "\n";
604
+ }
605
+
606
+ function mergePythonInitSource(existingSource: string | undefined, generatedSource: string): string {
607
+ if (!existingSource) {
608
+ return generatedSource;
609
+ }
610
+
611
+ const existingLines = existingSource.split(/\r?\n/);
612
+ const lineSet = new Set(existingLines);
613
+ const missingLines = generatedSource
614
+ .trim()
615
+ .split(/\r?\n/)
616
+ .filter((line) => line.length > 0 && !lineSet.has(line));
617
+
618
+ if (missingLines.length === 0) {
619
+ return existingSource.endsWith("\n") ? existingSource : `${existingSource}\n`;
620
+ }
621
+
622
+ const normalizedExisting = existingSource.endsWith("\n") ? existingSource : `${existingSource}\n`;
623
+ return `${normalizedExisting}${missingLines.join("\n")}\n`;
624
+ }
625
+
626
+ function writeMergedPythonInitFile(
627
+ filePath: string,
628
+ generatedSource: string,
629
+ session: FileOperationSession,
630
+ sourceModel: string
631
+ ): void {
632
+ const existingSource = session.fileExists(filePath) ? session.readFile(filePath) : undefined;
633
+ session.writeFile(filePath, mergePythonInitSource(existingSource, generatedSource), {
634
+ ownership: "extension-point",
635
+ generator: "native-schema",
636
+ sourceModel,
637
+ });
638
+ }
639
+
640
+ function ensureCSharpCodegenProjectFiles(
641
+ functionsRoot: string,
642
+ session: FileOperationSession,
643
+ sourceModel: string
644
+ ): void {
645
+ const toolManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
646
+ if (!session.fileExists(toolManifestPath)) {
647
+ session.writeFile(toolManifestPath, buildCSharpCodegenToolManifestSource(), {
648
+ ownership: "generated-once",
649
+ generator: "native-schema",
650
+ sourceModel,
651
+ });
652
+ }
653
+ }
654
+
655
+ function ensurePythonCodegenProjectFiles(
656
+ functionsRoot: string,
657
+ session: FileOperationSession,
658
+ sourceModel: string
659
+ ): string {
660
+ const requirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
661
+ if (!session.fileExists(requirementsPath)) {
662
+ session.writeFile(requirementsPath, buildPythonCodegenRequirementsSource(), {
663
+ ownership: "generated-once",
664
+ generator: "native-schema",
665
+ sourceModel,
666
+ });
667
+ }
668
+ return requirementsPath;
669
+ }
670
+
671
+ function getVirtualEnvPythonPath(venvDir: string): string {
672
+ return process.platform === "win32"
673
+ ? path.join(venvDir, "Scripts", "python.exe")
674
+ : path.join(venvDir, "bin", "python");
675
+ }
676
+
677
+ async function ensureProjectLocalUvCommand(projectRoot: string): Promise<{ command: string; env: NodeJS.ProcessEnv }> {
678
+ const uvEnv = buildProjectLocalUvEnv(process.env, projectRoot);
679
+ const { localUvExecutable } = getProjectLocalUvPaths(projectRoot);
680
+
681
+ if (canRun("uv", ["--version"], projectRoot)) {
682
+ return { command: "uv", env: uvEnv };
683
+ }
684
+
685
+ if (fs.existsSync(localUvExecutable) && canRun(localUvExecutable, ["--version"], projectRoot)) {
686
+ return { command: localUvExecutable, env: uvEnv };
687
+ }
688
+
689
+ const installer = getProjectLocalUvInstallerCommand();
690
+ await runCommand(
691
+ installer.command,
692
+ installer.args,
693
+ projectRoot,
694
+ "Failed to install project-local uv.",
695
+ buildProjectLocalUvInstallerEnv(process.env, projectRoot)
696
+ );
697
+
698
+ if (!(fs.existsSync(localUvExecutable) && canRun(localUvExecutable, ["--version"], projectRoot))) {
699
+ throw new Error("Failed to install project-local uv.");
700
+ }
701
+
702
+ return { command: localUvExecutable, env: uvEnv };
703
+ }
704
+
705
+ async function ensurePythonCodegenEnvironment(functionsRoot: string, requirementsPath: string): Promise<string> {
706
+ const projectRoot = getPythonProjectRoot(functionsRoot);
707
+ const { command: uvCommand, env: uvEnv } = await ensureProjectLocalUvCommand(projectRoot);
708
+ const venvDir = path.join(functionsRoot, ".codegen-venv");
709
+ const venvPython = getVirtualEnvPythonPath(venvDir);
710
+
711
+ if (!canRun(venvPython, ["--version"], functionsRoot, uvEnv)) {
712
+ const venvArgs = buildUvVenvArgs(venvDir);
713
+ if (fs.existsSync(venvDir)) {
714
+ venvArgs.push("--clear");
715
+ }
716
+
717
+ await runCommand(
718
+ uvCommand,
719
+ venvArgs,
720
+ functionsRoot,
721
+ "Failed to create the Python schema code generation virtual environment.",
722
+ uvEnv
723
+ );
724
+ }
725
+
726
+ if (!canRun(venvPython, ["-c", "import datamodel_code_generator"], functionsRoot, uvEnv)) {
727
+ await runCommand(
728
+ uvCommand,
729
+ buildUvPipInstallArgs(venvPython, requirementsPath),
730
+ functionsRoot,
731
+ "Failed to install Python schema generation dependencies.",
732
+ uvEnv
733
+ );
734
+ }
735
+
736
+ return venvPython;
737
+ }
738
+
739
+ /**
740
+ * 外部 codegen toolchain の事前検証。プロジェクトには一切書き込まず、
741
+ * OS 一時ディレクトリ上で NSwag / datamodel-code-generator を実行して失敗を早期検出する。
742
+ */
743
+ export async function validateNativeSchemaToolchain(
744
+ models: ModelInfo[],
745
+ rootModel: ModelInfo,
746
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
747
+ functionsRoot: string
748
+ ): Promise<void> {
749
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-native-codegen-"));
750
+ try {
751
+ const specPath = path.join(tempDir, `${toKebabCase(rootModel.name)}.openapi.json`);
752
+ fs.writeFileSync(specPath, generateOpenApiDocument(models, rootModel), "utf-8");
753
+
754
+ if (backendLanguage === "csharp") {
755
+ if (!canRun("dotnet", ["--version"], tempDir)) {
756
+ throw new Error(
757
+ "The .NET SDK is required to generate C# backend schema assets.\n" +
758
+ "Install the .NET 8 SDK and retry."
759
+ );
760
+ }
761
+
762
+ // プロジェクトの tool manifest(バージョン固定)があればそれを尊重し、なければ一時 manifest で検証する
763
+ const projectManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
764
+ let toolCwd = functionsRoot;
765
+ if (!fs.existsSync(projectManifestPath)) {
766
+ fs.mkdirSync(path.join(tempDir, ".config"), { recursive: true });
767
+ fs.writeFileSync(path.join(tempDir, ".config", "dotnet-tools.json"), buildCSharpCodegenToolManifestSource(), "utf-8");
768
+ toolCwd = tempDir;
769
+ }
770
+
771
+ await runCommand("dotnet", ["tool", "restore"], toolCwd, "Failed to restore the NSwag dotnet tool.");
772
+ await runCommand(
773
+ "dotnet",
774
+ getCSharpNativeGeneratorArgs(specPath, path.join(tempDir, "Contracts.cs")),
775
+ toolCwd,
776
+ "NSwag failed to generate C# backend schema assets."
777
+ );
778
+ } else {
779
+ const projectRequirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
780
+ let requirementsPath = projectRequirementsPath;
781
+ if (!fs.existsSync(projectRequirementsPath)) {
782
+ requirementsPath = path.join(tempDir, "requirements.codegen.txt");
783
+ fs.writeFileSync(requirementsPath, buildPythonCodegenRequirementsSource(), "utf-8");
784
+ }
785
+
786
+ // .codegen-venv は environment cache であり managed artifact ではない(既存挙動どおり functionsRoot 配下)
787
+ fs.mkdirSync(functionsRoot, { recursive: true });
788
+ const pythonExecutable = await ensurePythonCodegenEnvironment(functionsRoot, requirementsPath);
789
+ await runCommand(
790
+ pythonExecutable,
791
+ getPythonNativeGeneratorArgs(specPath, path.join(tempDir, "models.py")),
792
+ functionsRoot,
793
+ "datamodel-code-generator failed to generate Python backend schema assets."
794
+ );
795
+ }
796
+ } finally {
797
+ fs.rmSync(tempDir, { recursive: true, force: true });
798
+ }
799
+ }
800
+
801
+ async function generateCSharpSchemaArtifacts(
802
+ models: ModelInfo[],
803
+ outputDir: string,
804
+ functionsRoot: string,
805
+ session: FileOperationSession,
806
+ sourceModel: string
807
+ ): Promise<void> {
808
+ ensureCSharpCodegenProjectFiles(functionsRoot, session, sourceModel);
809
+
810
+ const optionPath = getCSharpSchemaOptionPath(outputDir);
811
+ session.writeFile(optionPath, generateLegacyCompatibleOptionSource(), {
812
+ ownership: "managed",
813
+ generator: "native-schema",
814
+ sourceModel,
815
+ });
816
+
817
+ for (const model of models) {
818
+ const modelPath = getCSharpSchemaModelPath(outputDir, model.name);
819
+ session.writeFile(modelPath, generateLegacyCompatibleCSharpModelSource(model), {
820
+ ownership: "managed",
821
+ generator: "native-schema",
822
+ sourceModel,
823
+ });
824
+ }
825
+ }
826
+
827
+ async function generatePythonSchemaArtifacts(
828
+ models: ModelInfo[],
829
+ outputDir: string,
830
+ functionsRoot: string,
831
+ session: FileOperationSession,
832
+ sourceModel: string
833
+ ): Promise<void> {
834
+ ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel);
835
+
836
+ const packageRoot = path.join(outputDir, "backend_models");
837
+ const modelsRoot = path.join(packageRoot, "models");
838
+ writeMergedPythonInitFile(
839
+ path.join(packageRoot, "__init__.py"),
840
+ buildGeneratedPythonPackageInitSource(models),
841
+ session,
842
+ sourceModel
843
+ );
844
+ writeMergedPythonInitFile(
845
+ path.join(modelsRoot, "__init__.py"),
846
+ buildGeneratedPythonModelsInitSource(models),
847
+ session,
848
+ sourceModel
849
+ );
850
+
851
+ for (const model of models) {
852
+ const modelPath = getPythonSchemaModelPath(outputDir, model.name);
853
+ session.writeFile(modelPath, generateLegacyCompatiblePythonModelSource(model), {
854
+ ownership: "managed",
855
+ generator: "native-schema",
856
+ sourceModel,
857
+ });
858
+ }
859
+ }
860
+
861
+ export async function generateLanguageSchemaArtifacts(
862
+ models: ModelInfo[],
863
+ rootModel: ModelInfo,
864
+ functionsDir: string,
865
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
866
+ session: FileOperationSession = new FileOperationSession("commit")
867
+ ): Promise<void> {
868
+ const planning = session.mode === "collect";
869
+ console.log(
870
+ planning
871
+ ? "\n\ud83e\uddec Planning OpenAPI export and native schema assets..."
872
+ : "\n\ud83e\uddec Generating OpenAPI export and native schema assets..."
873
+ );
874
+
875
+ const projectRoot = process.cwd();
876
+ const functionsRoot = path.join(projectRoot, functionsDir);
877
+ const openApiDir = path.join(functionsRoot, "openapi");
878
+
879
+ const specPath = path.join(openApiDir, `${toKebabCase(rootModel.name)}.openapi.json`);
880
+ session.writeFile(specPath, generateOpenApiDocument(models, rootModel), {
881
+ ownership: "managed",
882
+ generator: "native-schema",
883
+ sourceModel: rootModel.name,
884
+ });
885
+ console.log(planning ? `\ud83d\udccb Will create: ${specPath}` : `\u2705 Created: ${specPath}`);
886
+
887
+ const outputDir = path.join(
888
+ functionsRoot,
889
+ "generated",
890
+ backendLanguage === "csharp" ? "csharp-models" : "python-models"
891
+ );
892
+
893
+ if (backendLanguage === "csharp") {
894
+ await generateCSharpSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
895
+ } else {
896
+ await generatePythonSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
897
+ }
898
+
899
+ console.log(
900
+ planning
901
+ ? `\ud83d\udccb Will generate ${backendLanguage} schema assets: ${outputDir}`
902
+ : `\u2705 Generated ${backendLanguage} schema assets: ${outputDir}`
903
+ );
904
+ }