swallowkit 1.1.2 → 2.0.0-alpha.10

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 (262) hide show
  1. package/README.ja.md +61 -7
  2. package/README.md +61 -7
  3. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  4. package/dist/cli/commands/add-auth.js +103 -27
  5. package/dist/cli/commands/add-auth.js.map +1 -1
  6. package/dist/cli/commands/configure-mcp.d.ts +12 -0
  7. package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
  8. package/dist/cli/commands/configure-mcp.js +42 -0
  9. package/dist/cli/commands/configure-mcp.js.map +1 -0
  10. package/dist/cli/commands/dev.d.ts +5 -2
  11. package/dist/cli/commands/dev.d.ts.map +1 -1
  12. package/dist/cli/commands/dev.js +143 -67
  13. package/dist/cli/commands/dev.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts +14 -7
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +149 -170
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/migrations.d.ts.map +1 -1
  19. package/dist/cli/commands/migrations.js +25 -0
  20. package/dist/cli/commands/migrations.js.map +1 -1
  21. package/dist/cli/commands/provision.d.ts +5 -0
  22. package/dist/cli/commands/provision.d.ts.map +1 -1
  23. package/dist/cli/commands/provision.js +278 -55
  24. package/dist/cli/commands/provision.js.map +1 -1
  25. package/dist/cli/commands/scaffold.d.ts +5 -1
  26. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  27. package/dist/cli/commands/scaffold.js +123 -18
  28. package/dist/cli/commands/scaffold.js.map +1 -1
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +49 -8
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/core/config.d.ts +2 -1
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +19 -0
  36. package/dist/core/config.js.map +1 -1
  37. package/dist/core/operations/create-model.d.ts +2 -0
  38. package/dist/core/operations/create-model.d.ts.map +1 -1
  39. package/dist/core/operations/create-model.js +60 -8
  40. package/dist/core/operations/create-model.js.map +1 -1
  41. package/dist/core/operations/migration-generator.d.ts.map +1 -1
  42. package/dist/core/operations/migration-generator.js +12 -1
  43. package/dist/core/operations/migration-generator.js.map +1 -1
  44. package/dist/core/operations/model-migration.d.ts +22 -0
  45. package/dist/core/operations/model-migration.d.ts.map +1 -0
  46. package/dist/core/operations/model-migration.js +36 -0
  47. package/dist/core/operations/model-migration.js.map +1 -0
  48. package/dist/core/operations/provision-deployment.d.ts +52 -0
  49. package/dist/core/operations/provision-deployment.d.ts.map +1 -0
  50. package/dist/core/operations/provision-deployment.js +164 -0
  51. package/dist/core/operations/provision-deployment.js.map +1 -0
  52. package/dist/core/operations/provision-operations.d.ts +62 -6
  53. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  54. package/dist/core/operations/provision-operations.js +653 -115
  55. package/dist/core/operations/provision-operations.js.map +1 -1
  56. package/dist/core/operations/scaffold-apply.d.ts +1 -0
  57. package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
  58. package/dist/core/operations/scaffold-apply.js +1 -0
  59. package/dist/core/operations/scaffold-apply.js.map +1 -1
  60. package/dist/core/operations/scaffold-machine.d.ts +1 -0
  61. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  62. package/dist/core/operations/scaffold-machine.js.map +1 -1
  63. package/dist/core/operations/scaffold-plan.d.ts +2 -0
  64. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  65. package/dist/core/operations/scaffold-plan.js +3 -1
  66. package/dist/core/operations/scaffold-plan.js.map +1 -1
  67. package/dist/core/project/agent-instructions.d.ts +2 -1
  68. package/dist/core/project/agent-instructions.d.ts.map +1 -1
  69. package/dist/core/project/agent-instructions.js +15 -0
  70. package/dist/core/project/agent-instructions.js.map +1 -1
  71. package/dist/core/project/boundaries.d.ts +5 -2
  72. package/dist/core/project/boundaries.d.ts.map +1 -1
  73. package/dist/core/project/boundaries.js +51 -20
  74. package/dist/core/project/boundaries.js.map +1 -1
  75. package/dist/core/project/capabilities.d.ts +17 -0
  76. package/dist/core/project/capabilities.d.ts.map +1 -1
  77. package/dist/core/project/capabilities.js +45 -6
  78. package/dist/core/project/capabilities.js.map +1 -1
  79. package/dist/core/project/home-page.d.ts +4 -0
  80. package/dist/core/project/home-page.d.ts.map +1 -0
  81. package/dist/core/project/home-page.js +113 -0
  82. package/dist/core/project/home-page.js.map +1 -0
  83. package/dist/core/project/manifest.d.ts +3 -1
  84. package/dist/core/project/manifest.d.ts.map +1 -1
  85. package/dist/core/project/manifest.js +47 -25
  86. package/dist/core/project/manifest.js.map +1 -1
  87. package/dist/core/project/mcp-config.d.ts +26 -0
  88. package/dist/core/project/mcp-config.d.ts.map +1 -0
  89. package/dist/core/project/mcp-config.js +117 -0
  90. package/dist/core/project/mcp-config.js.map +1 -0
  91. package/dist/core/project/react-project.d.ts +26 -0
  92. package/dist/core/project/react-project.d.ts.map +1 -0
  93. package/dist/core/project/react-project.js +443 -0
  94. package/dist/core/project/react-project.js.map +1 -0
  95. package/dist/core/project/state.d.ts +7 -0
  96. package/dist/core/project/state.d.ts.map +1 -1
  97. package/dist/core/project/state.js +86 -0
  98. package/dist/core/project/state.js.map +1 -1
  99. package/dist/core/project/validation.d.ts.map +1 -1
  100. package/dist/core/project/validation.js +32 -10
  101. package/dist/core/project/validation.js.map +1 -1
  102. package/dist/core/project/workflows.d.ts.map +1 -1
  103. package/dist/core/project/workflows.js +53 -7
  104. package/dist/core/project/workflows.js.map +1 -1
  105. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/auth-generator.js +26 -27
  107. package/dist/core/scaffold/auth-generator.js.map +1 -1
  108. package/dist/core/scaffold/model-parser.d.ts +23 -0
  109. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  110. package/dist/core/scaffold/model-parser.js +407 -137
  111. package/dist/core/scaffold/model-parser.js.map +1 -1
  112. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  113. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  114. package/dist/core/scaffold/native-schema-generator.js +294 -91
  115. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  116. package/dist/core/scaffold/openapi-generator.d.ts +5 -0
  117. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
  118. package/dist/core/scaffold/openapi-generator.js +53 -3
  119. package/dist/core/scaffold/openapi-generator.js.map +1 -1
  120. package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
  121. package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
  122. package/dist/core/scaffold/react-bff-generator.js +252 -0
  123. package/dist/core/scaffold/react-bff-generator.js.map +1 -0
  124. package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
  125. package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
  126. package/dist/core/scaffold/react-ui-generator.js +41 -0
  127. package/dist/core/scaffold/react-ui-generator.js.map +1 -0
  128. package/dist/core/scaffold/ui-generator.d.ts +0 -3
  129. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  130. package/dist/core/scaffold/ui-generator.js +68 -7
  131. package/dist/core/scaffold/ui-generator.js.map +1 -1
  132. package/dist/core/verify/index.d.ts +4 -3
  133. package/dist/core/verify/index.d.ts.map +1 -1
  134. package/dist/core/verify/index.js +122 -5
  135. package/dist/core/verify/index.js.map +1 -1
  136. package/dist/index.d.ts +3 -3
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +3 -2
  139. package/dist/index.js.map +1 -1
  140. package/dist/machine/contracts.d.ts +1 -0
  141. package/dist/machine/contracts.d.ts.map +1 -1
  142. package/dist/machine/errors.d.ts +3 -2
  143. package/dist/machine/errors.d.ts.map +1 -1
  144. package/dist/machine/errors.js +2 -1
  145. package/dist/machine/errors.js.map +1 -1
  146. package/dist/machine/index.d.ts.map +1 -1
  147. package/dist/machine/index.js +61 -30
  148. package/dist/machine/index.js.map +1 -1
  149. package/dist/mcp/index.d.ts +6 -4
  150. package/dist/mcp/index.d.ts.map +1 -1
  151. package/dist/mcp/index.js +210 -25
  152. package/dist/mcp/index.js.map +1 -1
  153. package/dist/mcp/version-selection.d.ts +55 -0
  154. package/dist/mcp/version-selection.d.ts.map +1 -0
  155. package/dist/mcp/version-selection.js +152 -0
  156. package/dist/mcp/version-selection.js.map +1 -0
  157. package/dist/types/index.d.ts +4 -0
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/utils/azure-cli.d.ts +56 -0
  160. package/dist/utils/azure-cli.d.ts.map +1 -0
  161. package/dist/utils/azure-cli.js +326 -0
  162. package/dist/utils/azure-cli.js.map +1 -0
  163. package/dist/utils/azure-diagnostics.d.ts +16 -0
  164. package/dist/utils/azure-diagnostics.d.ts.map +1 -0
  165. package/dist/utils/azure-diagnostics.js +68 -0
  166. package/dist/utils/azure-diagnostics.js.map +1 -0
  167. package/dist/utils/package-manager.d.ts +2 -2
  168. package/dist/utils/package-manager.d.ts.map +1 -1
  169. package/dist/utils/package-manager.js +6 -3
  170. package/dist/utils/package-manager.js.map +1 -1
  171. package/dist/utils/python-uv.d.ts +5 -7
  172. package/dist/utils/python-uv.d.ts.map +1 -1
  173. package/dist/utils/python-uv.js +50 -24
  174. package/dist/utils/python-uv.js.map +1 -1
  175. package/dist/utils/react-dev.d.ts +4 -0
  176. package/dist/utils/react-dev.d.ts.map +1 -0
  177. package/dist/utils/react-dev.js +35 -0
  178. package/dist/utils/react-dev.js.map +1 -0
  179. package/dist/utils/semver.d.ts +14 -0
  180. package/dist/utils/semver.d.ts.map +1 -0
  181. package/dist/utils/semver.js +61 -0
  182. package/dist/utils/semver.js.map +1 -0
  183. package/package.json +7 -4
  184. package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
  185. package/src/__tests__/agent-instructions.test.ts +6 -1
  186. package/src/__tests__/agent-loop-phase34.test.ts +143 -8
  187. package/src/__tests__/auth.test.ts +9 -0
  188. package/src/__tests__/azure-cli.test.ts +202 -0
  189. package/src/__tests__/boundaries.test.ts +42 -0
  190. package/src/__tests__/csharp-schema.test.ts +261 -0
  191. package/src/__tests__/dev.test.ts +13 -6
  192. package/src/__tests__/fixtures/az wrapper.cmd +2 -0
  193. package/src/__tests__/fixtures/azure-cli.cjs +153 -0
  194. package/src/__tests__/fixtures/provision-runner.cjs +28 -0
  195. package/src/__tests__/generated-project-matrix.test.ts +789 -0
  196. package/src/__tests__/home-page.test.ts +50 -0
  197. package/src/__tests__/init.test.ts +145 -5
  198. package/src/__tests__/mcp-version.test.ts +262 -0
  199. package/src/__tests__/mcp.test.ts +120 -3
  200. package/src/__tests__/multi-auth.test.ts +115 -4
  201. package/src/__tests__/package-manager.test.ts +17 -2
  202. package/src/__tests__/plan-apply.test.ts +179 -0
  203. package/src/__tests__/provision-runtime.test.ts +722 -0
  204. package/src/__tests__/python-uv.test.ts +50 -14
  205. package/src/__tests__/python-venv.test.ts +48 -0
  206. package/src/__tests__/react-dev.test.ts +72 -0
  207. package/src/__tests__/react.test.ts +838 -0
  208. package/src/__tests__/release-metadata.test.ts +121 -0
  209. package/src/__tests__/schema-analysis.test.ts +329 -0
  210. package/src/cli/commands/add-auth.ts +103 -29
  211. package/src/cli/commands/configure-mcp.ts +54 -0
  212. package/src/cli/commands/dev.ts +145 -73
  213. package/src/cli/commands/init.ts +147 -172
  214. package/src/cli/commands/migrations.ts +22 -0
  215. package/src/cli/commands/provision.ts +294 -56
  216. package/src/cli/commands/scaffold.ts +145 -22
  217. package/src/cli/index.ts +46 -10
  218. package/src/core/config.ts +21 -1
  219. package/src/core/operations/create-model.ts +51 -13
  220. package/src/core/operations/migration-generator.ts +14 -1
  221. package/src/core/operations/model-migration.ts +52 -0
  222. package/src/core/operations/provision-deployment.ts +183 -0
  223. package/src/core/operations/provision-operations.ts +662 -154
  224. package/src/core/operations/scaffold-apply.ts +2 -0
  225. package/src/core/operations/scaffold-machine.ts +1 -0
  226. package/src/core/operations/scaffold-plan.ts +5 -1
  227. package/src/core/project/agent-instructions.ts +17 -1
  228. package/src/core/project/agent-templates/AGENTS.md +113 -15
  229. package/src/core/project/agent-templates/CLAUDE.md +14 -1
  230. package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
  231. package/src/core/project/agent-templates/copilot-instructions.md +15 -1
  232. package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
  233. package/src/core/project/boundaries.ts +62 -20
  234. package/src/core/project/capabilities.ts +63 -6
  235. package/src/core/project/home-page.ts +114 -0
  236. package/src/core/project/manifest.ts +52 -27
  237. package/src/core/project/mcp-config.ts +84 -0
  238. package/src/core/project/react-project.ts +529 -0
  239. package/src/core/project/state.ts +70 -0
  240. package/src/core/project/validation.ts +32 -10
  241. package/src/core/project/workflows.ts +53 -7
  242. package/src/core/scaffold/auth-generator.ts +26 -27
  243. package/src/core/scaffold/model-parser.ts +413 -129
  244. package/src/core/scaffold/native-schema-generator.ts +319 -103
  245. package/src/core/scaffold/openapi-generator.ts +63 -5
  246. package/src/core/scaffold/react-bff-generator.ts +292 -0
  247. package/src/core/scaffold/react-ui-generator.ts +51 -0
  248. package/src/core/scaffold/ui-generator.ts +70 -8
  249. package/src/core/verify/index.ts +118 -6
  250. package/src/index.ts +3 -1
  251. package/src/machine/contracts.ts +1 -0
  252. package/src/machine/errors.ts +4 -2
  253. package/src/machine/index.ts +67 -36
  254. package/src/mcp/index.ts +221 -31
  255. package/src/mcp/version-selection.ts +163 -0
  256. package/src/types/index.ts +4 -0
  257. package/src/utils/azure-cli.ts +315 -0
  258. package/src/utils/azure-diagnostics.ts +75 -0
  259. package/src/utils/package-manager.ts +7 -4
  260. package/src/utils/python-uv.ts +53 -33
  261. package/src/utils/react-dev.ts +31 -0
  262. package/src/utils/semver.ts +59 -0
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.PYTHON_SCHEMA_CODEGEN_REQUIREMENT = exports.NSWAG_CONSOLECORE_VERSION = void 0;
36
+ exports.PYTHON_SCHEMA_RUNTIME_REQUIREMENT = exports.PYTHON_SCHEMA_CODEGEN_REQUIREMENT = exports.NSWAG_CONSOLECORE_VERSION = void 0;
37
37
  exports.buildCSharpCodegenToolManifestSource = buildCSharpCodegenToolManifestSource;
38
38
  exports.buildPythonCodegenRequirementsSource = buildPythonCodegenRequirementsSource;
39
39
  exports.getCSharpSchemaModelPath = getCSharpSchemaModelPath;
@@ -41,6 +41,8 @@ exports.getCSharpSchemaOptionPath = getCSharpSchemaOptionPath;
41
41
  exports.getPythonSchemaModelPath = getPythonSchemaModelPath;
42
42
  exports.getCSharpNativeGeneratorArgs = getCSharpNativeGeneratorArgs;
43
43
  exports.getPythonNativeGeneratorArgs = getPythonNativeGeneratorArgs;
44
+ exports.toPythonRegex = toPythonRegex;
45
+ exports.getPythonRegexWarnings = getPythonRegexWarnings;
44
46
  exports.validateNativeSchemaToolchain = validateNativeSchemaToolchain;
45
47
  exports.generateLanguageSchemaArtifacts = generateLanguageSchemaArtifacts;
46
48
  const fs = __importStar(require("fs"));
@@ -54,6 +56,8 @@ const python_uv_1 = require("../../utils/python-uv");
54
56
  exports.NSWAG_CONSOLECORE_VERSION = "14.7.1";
55
57
  exports.PYTHON_SCHEMA_CODEGEN_REQUIREMENT = "datamodel-code-generator>=0.44.0,<1.0.0";
56
58
  const PYTHON_OUTPUT_MODEL_TYPE = "pydantic_v2.BaseModel";
59
+ // Preserve model syntax compatibility with existing deployments, not the tool's interpreter.
60
+ const PYTHON_MODEL_SYNTAX_VERSION = "3.11";
57
61
  function getMachineAwareStdio() {
58
62
  return process.env.SWALLOWKIT_MACHINE_OUTPUT === "1" ? "pipe" : "inherit";
59
63
  }
@@ -160,8 +164,22 @@ function buildCSharpEnumMemberMap(values, enumType) {
160
164
  function csharpStringLiteral(value) {
161
165
  return JSON.stringify(value);
162
166
  }
163
- function isDateLikeField(field) {
164
- return field.type === "date" || (field.type === "string" && field.name.toLowerCase().endsWith("at"));
167
+ /** Date/time types are used only when the Zod schema declares them (z.iso.datetime(), z.iso.date(), z.date()). */
168
+ function getCSharpScalarType(field) {
169
+ switch (field.type) {
170
+ case "string":
171
+ return field.format === "date-time" ? "DateTime" : field.format === "date" ? "DateOnly" : "string";
172
+ case "number":
173
+ return field.isInteger ? "long" : "double";
174
+ case "boolean":
175
+ return "bool";
176
+ case "date":
177
+ return "DateTime";
178
+ case "object":
179
+ return "Dictionary<string, object>";
180
+ default:
181
+ return "object";
182
+ }
165
183
  }
166
184
  function getCSharpSchemaModelPath(outputDir, modelName) {
167
185
  return path.join(outputDir, "src", "SwallowKitBackendModels", "Model", `${modelName}.cs`);
@@ -204,36 +222,24 @@ function getPythonNativeGeneratorArgs(specPath, outputPath) {
204
222
  "--output-model-type",
205
223
  PYTHON_OUTPUT_MODEL_TYPE,
206
224
  "--target-python-version",
207
- "3.11",
225
+ PYTHON_MODEL_SYNTAX_VERSION,
208
226
  "--disable-timestamp",
209
227
  "--use-union-operator",
210
228
  "--collapse-root-models",
211
229
  ];
212
230
  }
213
231
  function getCSharpFieldBaseType(field) {
232
+ // Arrays first: arrays of nested models or enums are lists of the element type.
233
+ if (field.isArray) {
234
+ return `List<${getCSharpArrayElementType(field)}>`;
235
+ }
214
236
  if (field.isNestedSchema && field.nestedModelName) {
215
237
  return field.nestedModelName;
216
238
  }
217
239
  if (field.enumValues?.length) {
218
240
  return `${toPascalIdentifier(field.name)}Enum`;
219
241
  }
220
- if (field.isArray) {
221
- return `List<${getCSharpArrayElementType(field)}>`;
222
- }
223
- switch (field.type) {
224
- case "string":
225
- return isDateLikeField(field) ? "DateTime" : "string";
226
- case "number":
227
- return "decimal";
228
- case "boolean":
229
- return "bool";
230
- case "date":
231
- return "DateTime";
232
- case "object":
233
- return "Dictionary<string, object>";
234
- default:
235
- return "object";
236
- }
242
+ return getCSharpScalarType(field);
237
243
  }
238
244
  function getCSharpArrayElementType(field) {
239
245
  if (field.isNestedSchema && field.nestedModelName) {
@@ -242,24 +248,11 @@ function getCSharpArrayElementType(field) {
242
248
  if (field.enumValues?.length) {
243
249
  return `${toPascalIdentifier(field.name)}Enum`;
244
250
  }
245
- switch (field.type) {
246
- case "string":
247
- return isDateLikeField(field) ? "DateTime" : "string";
248
- case "number":
249
- return "decimal";
250
- case "boolean":
251
- return "bool";
252
- case "date":
253
- return "DateTime";
254
- case "object":
255
- return "Dictionary<string, object>";
256
- default:
257
- return "object";
258
- }
251
+ return getCSharpScalarType(field);
259
252
  }
260
253
  function getCSharpPropertyType(field) {
261
254
  const baseType = getCSharpFieldBaseType(field);
262
- return field.isOptional ? `${baseType}?` : baseType;
255
+ return field.isOptional || field.isNullable ? `${baseType}?` : baseType;
263
256
  }
264
257
  function getCSharpOptionType(field) {
265
258
  return `Option<${getCSharpPropertyType(field)}>`;
@@ -316,11 +309,96 @@ function buildCSharpEnumToJsonCases(field, members) {
316
309
  .map((member) => ` if (value == ${enumType}.${member.name})\n return ${csharpStringLiteral(member.value)};`)
317
310
  .join("\n\n");
318
311
  }
312
+ function csharpNumberLiteral(value) {
313
+ return Number.isInteger(value) ? `${value}` : `${value}d`;
314
+ }
315
+ function csharpRegexOptions(flags) {
316
+ const options = [
317
+ flags?.includes("i") ? "RegexOptions.IgnoreCase" : null,
318
+ flags?.includes("m") ? "RegexOptions.Multiline" : null,
319
+ flags?.includes("s") ? "RegexOptions.Singleline" : null,
320
+ ].filter(Boolean);
321
+ return options.length > 0 ? options.join(" | ") : "RegexOptions.None";
322
+ }
323
+ function isCSharpReferenceType(field) {
324
+ if (field.isArray || field.isNestedSchema || field.type === "object")
325
+ return true;
326
+ if (field.enumValues?.length)
327
+ return false;
328
+ return field.type === "string" && field.format !== "date-time" && field.format !== "date";
329
+ }
330
+ /**
331
+ * Translate Zod constraints into IValidatableObject checks. Regexes use search semantics like
332
+ * Zod's RegExp.test. String formats (email/uuid/url) and value-type presence stay with Zod.
333
+ */
334
+ function buildCSharpValueChecks(field, accessor, member, optionalValue) {
335
+ const constraints = field.constraints ?? {};
336
+ const checks = [];
337
+ const add = (condition, message) => checks.push(`if (${condition})\n yield return new ValidationResult(${csharpStringLiteral(message)}, new[] { ${member} });`);
338
+ if (field.isNestedSchema && field.nestedModelName) {
339
+ // Pattern variables in an if condition stay in scope for the enclosing block, so keep names unique.
340
+ const variable = `validated${toPascalIdentifier(field.name)}${accessor === "item" ? "Item" : ""}`;
341
+ checks.push(`if (${accessor} is IValidatableObject ${variable})\n foreach (var ${variable}Result in ${variable}.Validate(new ValidationContext(${accessor})))\n yield return new ValidationResult(${variable}Result.ErrorMessage, ${variable}Result.MemberNames.Select(name => ${member} + "." + name).ToArray());`);
342
+ return checks;
343
+ }
344
+ if (field.enumValues?.length)
345
+ return checks;
346
+ if (field.type === "string" && field.format !== "date-time" && field.format !== "date") {
347
+ if (constraints.minLength !== undefined)
348
+ add(`${accessor} != null && ${accessor}.Length < ${constraints.minLength}`, `${field.name} must contain at least ${constraints.minLength} character(s).`);
349
+ if (constraints.maxLength !== undefined)
350
+ add(`${accessor} != null && ${accessor}.Length > ${constraints.maxLength}`, `${field.name} must contain at most ${constraints.maxLength} character(s).`);
351
+ if (constraints.pattern !== undefined) {
352
+ add(`${accessor} != null && !Regex.IsMatch(${accessor}, ${csharpStringLiteral(constraints.pattern)}, ${csharpRegexOptions(constraints.patternFlags)})`, `${field.name} must match /${constraints.pattern}/${constraints.patternFlags ?? ""}.`);
353
+ }
354
+ }
355
+ if (field.type === "number") {
356
+ const value = optionalValue ? `${accessor}.Value` : accessor;
357
+ const guard = optionalValue ? `${accessor}.HasValue && ` : "";
358
+ if (constraints.minimum !== undefined) {
359
+ add(`${guard}${value} ${constraints.exclusiveMinimum ? "<=" : "<"} ${csharpNumberLiteral(constraints.minimum)}`, `${field.name} must be ${constraints.exclusiveMinimum ? "greater than" : "greater than or equal to"} ${constraints.minimum}.`);
360
+ }
361
+ if (constraints.maximum !== undefined) {
362
+ add(`${guard}${value} ${constraints.exclusiveMaximum ? ">=" : ">"} ${csharpNumberLiteral(constraints.maximum)}`, `${field.name} must be ${constraints.exclusiveMaximum ? "less than" : "less than or equal to"} ${constraints.maximum}.`);
363
+ }
364
+ }
365
+ return checks;
366
+ }
367
+ function buildCSharpValidationBody(model) {
368
+ const blocks = [];
369
+ for (const field of model.fields) {
370
+ const propertyName = toPascalIdentifier(field.name);
371
+ const member = csharpStringLiteral(field.name);
372
+ const constraints = field.constraints ?? {};
373
+ // .nullable() fields accept null in Zod, so only non-nullable required references are rejected.
374
+ if (!field.isOptional && !field.isNullable && isCSharpReferenceType(field)) {
375
+ blocks.push(`if (${propertyName} is null)\n yield return new ValidationResult(${csharpStringLiteral(`${field.name} is required.`)}, new[] { ${member} });`);
376
+ }
377
+ if (field.isArray) {
378
+ if (constraints.minItems !== undefined) {
379
+ blocks.push(`if (${propertyName} != null && ${propertyName}.Count < ${constraints.minItems})\n yield return new ValidationResult(${csharpStringLiteral(`${field.name} must contain at least ${constraints.minItems} item(s).`)}, new[] { ${member} });`);
380
+ }
381
+ if (constraints.maxItems !== undefined) {
382
+ blocks.push(`if (${propertyName} != null && ${propertyName}.Count > ${constraints.maxItems})\n yield return new ValidationResult(${csharpStringLiteral(`${field.name} must contain at most ${constraints.maxItems} item(s).`)}, new[] { ${member} });`);
383
+ }
384
+ const itemChecks = buildCSharpValueChecks({ ...field, isArray: false }, "item", `${member} + "[" + index + "]"`, false);
385
+ if (itemChecks.length > 0) {
386
+ blocks.push(`if (${propertyName} != null)\n {\n for (var index = 0; index < ${propertyName}.Count; index++)\n {\n var item = ${propertyName}[index];\n${itemChecks.map((check) => ` ${check.replace(/\n {16}/g, "\n ").replace(/\n {20}/g, "\n ")}`).join("\n")}\n }\n }`);
387
+ }
388
+ continue;
389
+ }
390
+ const optionalValue = (field.isOptional || field.isNullable === true) && !isCSharpReferenceType(field);
391
+ blocks.push(...buildCSharpValueChecks(field, propertyName, member, optionalValue));
392
+ }
393
+ return blocks.length > 0
394
+ ? blocks.map((block) => ` ${block}`).join("\n\n")
395
+ : " yield break;";
396
+ }
319
397
  function generateLegacyCompatibleCSharpModelSource(model) {
320
398
  const requiredFields = model.fields.filter((field) => !field.isOptional);
321
399
  const optionalFields = model.fields.filter((field) => field.isOptional);
322
400
  const constructorParams = [
323
- ...requiredFields.map((field) => `${getCSharpFieldBaseType(field)} ${field.name}`),
401
+ ...requiredFields.map((field) => `${getCSharpPropertyType(field)} ${field.name}`),
324
402
  ...optionalFields.map((field) => `${getCSharpOptionType(field)} ${field.name} = default`),
325
403
  ].join(", ");
326
404
  const constructorAssignments = [
@@ -336,7 +414,7 @@ function generateLegacyCompatibleCSharpModelSource(model) {
336
414
  return ` /// <summary>
337
415
  /// Defines ${toPascalIdentifier(field.name)}
338
416
  /// </summary>
339
- [JsonConverter(typeof(JsonStringEnumConverter))]
417
+ [JsonConverter(typeof(${enumType}JsonConverter))]
340
418
  public enum ${enumType}
341
419
  {
342
420
  ${buildCSharpEnumMembers(enumMembers)}
@@ -357,6 +435,22 @@ ${buildCSharpEnumFromStringCases(field, enumMembers, true)}
357
435
  ${buildCSharpEnumToJsonCases(field, enumMembers)}
358
436
 
359
437
  throw new NotImplementedException($"Value could not be handled: '{value}'");
438
+ }
439
+
440
+ /// <summary>
441
+ /// Reads and writes ${enumType} with the exact Zod enum values.
442
+ /// </summary>
443
+ public sealed class ${enumType}JsonConverter : JsonConverter<${enumType}>
444
+ {
445
+ public override ${enumType} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
446
+ {
447
+ var value = reader.GetString();
448
+ var parsed = value == null ? null : ${enumType}FromStringOrDefault(value);
449
+ return parsed ?? throw new JsonException($"Unsupported ${enumType} value: '{value}'");
450
+ }
451
+
452
+ public override void Write(Utf8JsonWriter writer, ${enumType} value, JsonSerializerOptions options)
453
+ => writer.WriteStringValue(${enumType}ToJsonValue(value));
360
454
  }`;
361
455
  })
362
456
  .join("\n\n");
@@ -381,7 +475,8 @@ ${buildCSharpEnumToJsonCases(field, enumMembers)}
381
475
  /// <summary>
382
476
  /// Gets or Sets ${propertyName}
383
477
  /// </summary>
384
- [JsonPropertyName("${field.name}")]
478
+ [JsonPropertyName("${field.name}")]${field.isNullable ? "" : `
479
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`}
385
480
  public ${propertyType} ${propertyName} { get { return this.${propertyName}Option; } set { this.${propertyName}Option = new(value); } }`;
386
481
  })
387
482
  .join("\n\n");
@@ -403,8 +498,11 @@ ${buildCSharpEnumToJsonCases(field, enumMembers)}
403
498
  using System;
404
499
  using System.Collections.Generic;
405
500
  using System.ComponentModel.DataAnnotations;
501
+ using System.Linq;
406
502
  using System.Text;
503
+ using System.Text.Json;
407
504
  using System.Text.Json.Serialization;
505
+ using System.Text.RegularExpressions;
408
506
  using SwallowKitBackendModels.Client;
409
507
 
410
508
  namespace SwallowKitBackendModels.Model
@@ -414,12 +512,22 @@ namespace SwallowKitBackendModels.Model
414
512
  /// </summary>
415
513
  public partial class ${model.name} : IValidatableObject
416
514
  {
515
+ /// <summary>
516
+ /// Used by System.Text.Json. Optional values are marked as set only when present in the JSON.
517
+ /// </summary>
518
+ #pragma warning disable CS8618
417
519
  [JsonConstructor]
520
+ public ${model.name}()
521
+ {
522
+ OnCreated();
523
+ }
524
+ #pragma warning restore CS8618
525
+ ${constructorParams ? `
418
526
  public ${model.name}(${constructorParams})
419
527
  {
420
528
  ${constructorAssignments}
421
529
  }
422
-
530
+ ` : ""}
423
531
  partial void OnCreated();
424
532
  ${enumBlocks ? `\n\n${enumBlocks}` : ""}
425
533
 
@@ -436,24 +544,18 @@ ${toStringBody}
436
544
 
437
545
  IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
438
546
  {
439
- yield break;
547
+ ${buildCSharpValidationBody(model)}
440
548
  }
441
549
  }
442
550
  }
443
551
  `;
444
552
  }
445
- function getPythonTypeName(field) {
446
- if (field.isNestedSchema && field.nestedModelName) {
447
- return field.nestedModelName;
448
- }
449
- if (field.isArray) {
450
- return `List[${getPythonArrayElementType(field)}]`;
451
- }
553
+ function getPythonScalarType(field) {
452
554
  switch (field.type) {
453
555
  case "string":
454
- return isDateLikeField(field) ? "datetime" : "StrictStr";
556
+ return field.format === "date-time" ? "datetime" : field.format === "date" ? "date" : "StrictStr";
455
557
  case "number":
456
- return "Union[StrictFloat, StrictInt]";
558
+ return field.isInteger ? "StrictInt" : "Union[StrictFloat, StrictInt]";
457
559
  case "boolean":
458
560
  return "StrictBool";
459
561
  case "date":
@@ -464,36 +566,128 @@ function getPythonTypeName(field) {
464
566
  return "Any";
465
567
  }
466
568
  }
569
+ /** pydantic Field bounds for a scalar value (string length or numeric range). */
570
+ function getPythonValueConstraintArgs(field) {
571
+ const constraints = field.constraints ?? {};
572
+ if (field.enumValues?.length || field.isNestedSchema)
573
+ return [];
574
+ const args = [];
575
+ if (field.type === "string" && field.format !== "date-time" && field.format !== "date") {
576
+ if (constraints.minLength !== undefined)
577
+ args.push(`min_length=${constraints.minLength}`);
578
+ if (constraints.maxLength !== undefined)
579
+ args.push(`max_length=${constraints.maxLength}`);
580
+ }
581
+ if (field.type === "number") {
582
+ if (constraints.minimum !== undefined)
583
+ args.push(`${constraints.exclusiveMinimum ? "gt" : "ge"}=${constraints.minimum}`);
584
+ if (constraints.maximum !== undefined)
585
+ args.push(`${constraints.exclusiveMaximum ? "lt" : "le"}=${constraints.maximum}`);
586
+ }
587
+ return args;
588
+ }
467
589
  function getPythonArrayElementType(field) {
468
590
  if (field.isNestedSchema && field.nestedModelName) {
469
591
  return field.nestedModelName;
470
592
  }
471
- switch (field.type) {
472
- case "string":
473
- return isDateLikeField(field) ? "datetime" : "StrictStr";
474
- case "number":
475
- return "Union[StrictFloat, StrictInt]";
476
- case "boolean":
477
- return "StrictBool";
478
- case "date":
479
- return "datetime";
480
- case "object":
481
- return "Dict[str, Any]";
482
- default:
483
- return "Any";
593
+ const elementType = getPythonScalarType(field);
594
+ const elementArgs = getPythonValueConstraintArgs(field);
595
+ return elementArgs.length > 0 ? `Annotated[${elementType}, Field(${elementArgs.join(", ")})]` : elementType;
596
+ }
597
+ function getPythonTypeName(field) {
598
+ if (field.isArray) {
599
+ return `List[${getPythonArrayElementType(field)}]`;
600
+ }
601
+ if (field.isNestedSchema && field.nestedModelName) {
602
+ return field.nestedModelName;
484
603
  }
604
+ return getPythonScalarType(field);
485
605
  }
486
606
  function buildPythonFieldDeclaration(field) {
487
607
  const pythonName = toSnakeCase(field.name);
488
608
  const typeName = field.isOptional ? `Optional[${getPythonTypeName(field)}]` : getPythonTypeName(field);
489
- const aliasSuffix = pythonName !== field.name ? `, alias="${field.name}"` : "";
490
- if (field.isOptional) {
491
- return `${pythonName}: ${typeName} = Field(default=None${aliasSuffix})`;
492
- }
493
- if (aliasSuffix) {
494
- return `${pythonName}: ${typeName} = Field(${aliasSuffix.slice(2)})`;
495
- }
496
- return `${pythonName}: ${typeName}`;
609
+ const fieldArgs = [
610
+ ...(field.isOptional ? ["default=None"] : []),
611
+ ...(pythonName !== field.name ? [`alias="${field.name}"`] : []),
612
+ ...(field.isArray
613
+ ? [
614
+ ...(field.constraints?.minItems !== undefined ? [`min_length=${field.constraints.minItems}`] : []),
615
+ ...(field.constraints?.maxItems !== undefined ? [`max_length=${field.constraints.maxItems}`] : []),
616
+ ]
617
+ : getPythonValueConstraintArgs(field)),
618
+ ];
619
+ return fieldArgs.length > 0
620
+ ? `${pythonName}: ${typeName} = Field(${fieldArgs.join(", ")})`
621
+ : `${pythonName}: ${typeName}`;
622
+ }
623
+ function buildPythonRegexFlags(flags) {
624
+ const values = [
625
+ flags?.includes("i") ? "re.IGNORECASE" : null,
626
+ flags?.includes("m") ? "re.MULTILINE" : null,
627
+ flags?.includes("s") ? "re.DOTALL" : null,
628
+ ].filter(Boolean);
629
+ return values.length > 0 ? values.join(" | ") : "0";
630
+ }
631
+ /**
632
+ * Translates a JavaScript regex (as used by Zod) to Python `re` syntax. Constructs Python cannot
633
+ * express identically are reported as unsupported so the validator is skipped instead of breaking imports.
634
+ */
635
+ function toPythonRegex(pattern, flags = "") {
636
+ const unsupportedFlags = [...flags].filter((flag) => !"imsgu".includes(flag));
637
+ if (unsupportedFlags.length > 0)
638
+ return { unsupported: `flag(s) ${unsupportedFlags.join("")} have no Python equivalent` };
639
+ const checks = [
640
+ [/\\[pP]\{/, "Unicode property escapes (\\p{...})"],
641
+ [/\\u\{/, "code point escapes (\\u{...})"],
642
+ [/\\c[A-Za-z]/, "control escapes (\\cX)"],
643
+ [/\(\?<[=!]/, "lookbehind (JavaScript allows variable width; Python does not)"],
644
+ [/\[\^?\]/, "empty character classes ([] / [^])"],
645
+ ];
646
+ for (const [construct, label] of checks) {
647
+ if (construct.test(pattern))
648
+ return { unsupported: label };
649
+ }
650
+ return {
651
+ pattern: pattern
652
+ .replace(/\(\?<([A-Za-z_][A-Za-z0-9_]*)>/g, "(?P<$1>")
653
+ .replace(/\\k<([A-Za-z_][A-Za-z0-9_]*)>/g, "(?P=$1)"),
654
+ };
655
+ }
656
+ function pythonRegexFields(model) {
657
+ return model.fields
658
+ .filter((field) => field.type === "string" && !field.enumValues?.length && field.constraints?.pattern !== undefined)
659
+ .map((field) => ({ field, translation: toPythonRegex(field.constraints.pattern, field.constraints.patternFlags) }));
660
+ }
661
+ function getPythonRegexWarnings(model) {
662
+ return pythonRegexFields(model)
663
+ .filter((entry) => "unsupported" in entry.translation)
664
+ .map(({ field, translation }) => `python-regex-not-enforced: ${model.name}.${field.name} uses ${translation.unsupported}; the Python model does not enforce /${field.constraints.pattern}/. The BFF Zod schema still validates it.`);
665
+ }
666
+ /**
667
+ * Zod regex checks use RegExp.test (search), so validators use re.search instead of pydantic's anchored pattern.
668
+ * They run before type conversion so date/time strings are checked as the JSON text Zod sees; non-string
669
+ * values are left to the field's own type validation.
670
+ */
671
+ function buildPythonPatternValidators(model) {
672
+ return pythonRegexFields(model)
673
+ .filter((entry) => "pattern" in entry.translation)
674
+ .map(({ field, translation }) => {
675
+ const pythonName = toSnakeCase(field.name);
676
+ const pattern = JSON.stringify(translation.pattern);
677
+ const flags = buildPythonRegexFlags(field.constraints.patternFlags);
678
+ const message = JSON.stringify(`${field.name} must match /${field.constraints.pattern}/${field.constraints.patternFlags ?? ""}`);
679
+ return ` @field_validator('${pythonName}', mode='before')
680
+ def ${pythonName}_validate_pattern(cls, value):
681
+ """Validates the Zod regex constraint"""
682
+ if value is None:
683
+ return value
684
+
685
+ for item in (value if isinstance(value, list) else [value]):
686
+ if isinstance(item, str) and not re.search(${pattern}, item, ${flags}):
687
+ raise ValueError(${message})
688
+ return value`;
689
+ })
690
+ .join("\n\n");
497
691
  }
498
692
  function buildPythonEnumValidators(model) {
499
693
  return model.fields
@@ -524,7 +718,7 @@ function buildPythonModelImports(model) {
524
718
  function generateLegacyCompatiblePythonModelSource(model) {
525
719
  const fieldDeclarations = model.fields.map((field) => ` ${buildPythonFieldDeclaration(field)}`).join("\n");
526
720
  const propertyNames = model.fields.map((field) => `"${field.name}"`).join(", ");
527
- const validators = buildPythonEnumValidators(model);
721
+ const validators = [buildPythonEnumValidators(model), buildPythonPatternValidators(model)].filter(Boolean).join("\n\n");
528
722
  const dictAssignments = model.fields
529
723
  .map((field) => ` "${field.name}": obj.get("${field.name}")`)
530
724
  .join(",\n");
@@ -547,9 +741,9 @@ import pprint
547
741
  import re # noqa: F401
548
742
  import json
549
743
 
550
- from datetime import datetime
744
+ from datetime import date, datetime
551
745
  from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, field_validator
552
- from typing import Any, ClassVar, Dict, List, Optional, Set, Union
746
+ from typing import Annotated, Any, ClassVar, Dict, List, Optional, Set, Union
553
747
  from typing_extensions import Self
554
748
 
555
749
  ${buildPythonModelImports(model)}class ${model.name}(BaseModel):
@@ -573,7 +767,7 @@ ${validators ? `\n\n${validators}` : ""}
573
767
 
574
768
  def to_json(self) -> str:
575
769
  """Returns the JSON representation of the model using alias"""
576
- return json.dumps(self.to_dict())
770
+ return self.model_dump_json(by_alias=True, exclude_none=True)
577
771
 
578
772
  @classmethod
579
773
  def from_json(cls, json_str: str) -> Optional[Self]:
@@ -660,13 +854,22 @@ function ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel) {
660
854
  }
661
855
  return requirementsPath;
662
856
  }
663
- function getVirtualEnvPythonPath(venvDir) {
664
- return process.platform === "win32"
665
- ? path.join(venvDir, "Scripts", "python.exe")
666
- : path.join(venvDir, "bin", "python");
857
+ /** Generated backend_models import pydantic v2 at runtime, so the deployed Functions app must install it. */
858
+ exports.PYTHON_SCHEMA_RUNTIME_REQUIREMENT = "pydantic>=2.6,<3";
859
+ function ensurePythonRuntimeRequirement(functionsRoot, session) {
860
+ const requirementsPath = path.join(functionsRoot, "requirements.txt");
861
+ const current = session.fileExists(requirementsPath) ? session.readFile(requirementsPath) : "";
862
+ const declared = current.split(/\r?\n/).some((line) => line.trim().split(/[<>=!~;[ ]/)[0].toLowerCase() === "pydantic");
863
+ if (declared)
864
+ return;
865
+ const prefix = current && !current.endsWith("\n") ? `${current}\n` : current;
866
+ session.writeFile(requirementsPath, `${prefix}${exports.PYTHON_SCHEMA_RUNTIME_REQUIREMENT}\n`, {
867
+ ownership: "extension-point",
868
+ generator: "native-schema",
869
+ });
667
870
  }
668
871
  async function ensureProjectLocalUvCommand(projectRoot) {
669
- const uvEnv = (0, python_uv_1.buildProjectLocalUvEnv)(process.env, projectRoot);
872
+ const uvEnv = { ...process.env };
670
873
  const { localUvExecutable } = (0, python_uv_1.getProjectLocalUvPaths)(projectRoot);
671
874
  if (canRun("uv", ["--version"], projectRoot)) {
672
875
  return { command: "uv", env: uvEnv };
@@ -685,16 +888,9 @@ async function ensurePythonCodegenEnvironment(functionsRoot, requirementsPath) {
685
888
  const projectRoot = (0, python_uv_1.getPythonProjectRoot)(functionsRoot);
686
889
  const { command: uvCommand, env: uvEnv } = await ensureProjectLocalUvCommand(projectRoot);
687
890
  const venvDir = path.join(functionsRoot, ".codegen-venv");
688
- const venvPython = getVirtualEnvPythonPath(venvDir);
689
- if (!canRun(venvPython, ["--version"], functionsRoot, uvEnv)) {
690
- const venvArgs = (0, python_uv_1.buildUvVenvArgs)(venvDir);
691
- if (fs.existsSync(venvDir)) {
692
- venvArgs.push("--clear");
693
- }
694
- await runCommand(uvCommand, venvArgs, functionsRoot, "Failed to create the Python schema code generation virtual environment.", uvEnv);
695
- }
891
+ const venvPython = await (0, python_uv_1.ensureUvVirtualEnvironment)(uvCommand, venvDir, functionsRoot, uvEnv);
696
892
  if (!canRun(venvPython, ["-c", "import datamodel_code_generator"], functionsRoot, uvEnv)) {
697
- await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(venvPython, requirementsPath), functionsRoot, "Failed to install Python schema generation dependencies.", uvEnv);
893
+ await runCommand(uvCommand, (0, python_uv_1.buildUvPipInstallArgs)(venvPython, requirementsPath), functionsRoot, "Failed to install Python schema generation dependencies.", (0, python_uv_1.buildPythonCodegenInstallEnv)(uvEnv));
698
894
  }
699
895
  return venvPython;
700
896
  }
@@ -759,11 +955,16 @@ async function generateCSharpSchemaArtifacts(models, outputDir, functionsRoot, s
759
955
  }
760
956
  async function generatePythonSchemaArtifacts(models, outputDir, functionsRoot, session, sourceModel) {
761
957
  ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel);
958
+ ensurePythonRuntimeRequirement(functionsRoot, session);
762
959
  const packageRoot = path.join(outputDir, "backend_models");
763
960
  const modelsRoot = path.join(packageRoot, "models");
764
961
  writeMergedPythonInitFile(path.join(packageRoot, "__init__.py"), buildGeneratedPythonPackageInitSource(models), session, sourceModel);
765
962
  writeMergedPythonInitFile(path.join(modelsRoot, "__init__.py"), buildGeneratedPythonModelsInitSource(models), session, sourceModel);
766
963
  for (const model of models) {
964
+ for (const warning of getPythonRegexWarnings(model)) {
965
+ session.addWarning(warning);
966
+ console.warn(`⚠️ ${warning}`);
967
+ }
767
968
  const modelPath = getPythonSchemaModelPath(outputDir, model.name);
768
969
  session.writeFile(modelPath, generateLegacyCompatiblePythonModelSource(model), {
769
970
  ownership: "managed",
@@ -788,11 +989,13 @@ async function generateLanguageSchemaArtifacts(models, rootModel, functionsDir,
788
989
  });
789
990
  console.log(planning ? `\ud83d\udccb Will create: ${specPath}` : `\u2705 Created: ${specPath}`);
790
991
  const outputDir = path.join(functionsRoot, "generated", backendLanguage === "csharp" ? "csharp-models" : "python-models");
992
+ // Inline z.object fields become named classes (e.g. ProbeItemNested) so their shape is preserved.
993
+ const nativeModels = (0, openapi_generator_1.expandInlineObjectModels)(models);
791
994
  if (backendLanguage === "csharp") {
792
- await generateCSharpSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
995
+ await generateCSharpSchemaArtifacts(nativeModels, outputDir, functionsRoot, session, rootModel.name);
793
996
  }
794
997
  else {
795
- await generatePythonSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
998
+ await generatePythonSchemaArtifacts(nativeModels, outputDir, functionsRoot, session, rootModel.name);
796
999
  }
797
1000
  console.log(planning
798
1001
  ? `\ud83d\udccb Will generate ${backendLanguage} schema assets: ${outputDir}`