@aws/nx-plugin 1.0.0-rc.93 → 1.0.0-rc.94

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 (101) hide show
  1. package/migrations.json +16 -1
  2. package/package.json +1 -1
  3. package/src/agentcore-harness/generator.d.ts +1 -1
  4. package/src/infra/app/__snapshots__/generator.spec.ts.snap +5 -5
  5. package/src/infra/app/generator.d.ts +1 -1
  6. package/src/infra/app/generator.js +5 -3
  7. package/src/infra/app/generator.js.map +1 -1
  8. package/src/init/generator.d.ts +1 -1
  9. package/src/migrations/latest/agent-core-vendor-target/metadata.json +3 -0
  10. package/src/migrations/latest/agent-core-vendor-target/migration.d.ts +6 -0
  11. package/src/migrations/latest/agent-core-vendor-target/migration.js +98 -0
  12. package/src/migrations/latest/agent-core-vendor-target/migration.js.map +1 -0
  13. package/src/migrations/latest/infra-deploy-express-mode/metadata.json +3 -0
  14. package/src/migrations/latest/infra-deploy-express-mode/migration.d.ts +6 -0
  15. package/src/migrations/latest/infra-deploy-express-mode/migration.js +83 -0
  16. package/src/migrations/latest/infra-deploy-express-mode/migration.js.map +1 -0
  17. package/src/migrations/latest/shx-fs-commands/metadata.json +3 -0
  18. package/src/migrations/latest/shx-fs-commands/migration.d.ts +6 -0
  19. package/src/migrations/latest/shx-fs-commands/migration.js +216 -0
  20. package/src/migrations/latest/shx-fs-commands/migration.js.map +1 -0
  21. package/src/open-api/ts-client/__snapshots__/generator.complex-types.spec.ts.snap +18 -12
  22. package/src/open-api/ts-client/__snapshots__/generator.edge-cases.spec.ts.snap +5 -2
  23. package/src/open-api/utils/codegen-data/types.d.ts +6 -0
  24. package/src/open-api/utils/codegen-data/types.js.map +1 -1
  25. package/src/open-api/utils/codegen-data.js +32 -7
  26. package/src/open-api/utils/codegen-data.js.map +1 -1
  27. package/src/open-api/utils/normalise.js +39 -14
  28. package/src/open-api/utils/normalise.js.map +1 -1
  29. package/src/open-api/utils/parser.js +51 -5
  30. package/src/open-api/utils/parser.js.map +1 -1
  31. package/src/preset/generator.d.ts +1 -1
  32. package/src/py/agent/a2a-connection/generator.d.ts +1 -1
  33. package/src/py/agent/gateway-connection/generator.d.ts +1 -1
  34. package/src/py/agent/generator.d.ts +2 -26
  35. package/src/py/agent/generator.js +2 -2
  36. package/src/py/agent/generator.js.map +1 -1
  37. package/src/py/agent/mcp-connection/generator.d.ts +1 -1
  38. package/src/py/agent/react-connection/generator.d.ts +1 -1
  39. package/src/py/fast-api/generator.d.ts +1 -13
  40. package/src/py/fast-api/generator.js +1 -1
  41. package/src/py/fast-api/generator.js.map +1 -1
  42. package/src/py/fast-api/react/generator.d.ts +1 -1
  43. package/src/py/mcp-server/generator.d.ts +2 -26
  44. package/src/py/mcp-server/generator.js +2 -2
  45. package/src/py/mcp-server/generator.js.map +1 -1
  46. package/src/py/rdb/generator.d.ts +2 -26
  47. package/src/py/rdb/generator.js +6 -6
  48. package/src/py/rdb/generator.js.map +1 -1
  49. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +24 -20
  50. package/src/smithy/project/generator.d.ts +2 -14
  51. package/src/smithy/project/generator.js +2 -1
  52. package/src/smithy/project/generator.js.map +1 -1
  53. package/src/smithy/react-connection/generator.d.ts +1 -1
  54. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -7
  55. package/src/smithy/ts/api/generator.d.ts +1 -13
  56. package/src/smithy/ts/api/generator.js +1 -1
  57. package/src/smithy/ts/api/generator.js.map +1 -1
  58. package/src/terraform/project/generator.d.ts +2 -2
  59. package/src/terraform/project/generator.js +4 -4
  60. package/src/terraform/project/generator.js.map +1 -1
  61. package/src/trpc/react/generator.d.ts +1 -1
  62. package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
  63. package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
  64. package/src/ts/agent/generator.d.ts +3 -39
  65. package/src/ts/agent/generator.js +1 -1
  66. package/src/ts/agent/generator.js.map +1 -1
  67. package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
  68. package/src/ts/agent/react-connection/generator.d.ts +1 -1
  69. package/src/ts/astro-docs/generator.d.ts +1 -1
  70. package/src/ts/dcr-proxy/generator.d.ts +1 -1
  71. package/src/ts/dynamodb/generator.d.ts +1 -1
  72. package/src/ts/lambda-function/generator.d.ts +1 -1
  73. package/src/ts/lib/generator.d.ts +1 -1
  74. package/src/ts/mcp-server/generator.d.ts +3 -39
  75. package/src/ts/mcp-server/generator.js +1 -1
  76. package/src/ts/mcp-server/generator.js.map +1 -1
  77. package/src/ts/nx-generator/generator.d.ts +1 -1
  78. package/src/ts/nx-migration/generator.d.ts +1 -1
  79. package/src/ts/nx-plugin/generator.d.ts +1 -1
  80. package/src/ts/rdb/generator.d.ts +3 -27
  81. package/src/ts/rdb/generator.js +3 -3
  82. package/src/ts/rdb/generator.js.map +1 -1
  83. package/src/ts/react-website/agui/generator.d.ts +1 -1
  84. package/src/utils/agent-core-packaging.d.ts +25 -12
  85. package/src/utils/agent-core-packaging.js +47 -11
  86. package/src/utils/agent-core-packaging.js.map +1 -1
  87. package/src/utils/docker.d.ts +1 -7
  88. package/src/utils/docker.js +1 -1
  89. package/src/utils/docker.js.map +1 -1
  90. package/src/utils/format.d.ts +5 -0
  91. package/src/utils/format.js +5 -0
  92. package/src/utils/format.js.map +1 -1
  93. package/src/utils/fs.d.ts +21 -11
  94. package/src/utils/fs.js +30 -22
  95. package/src/utils/fs.js.map +1 -1
  96. package/src/utils/version-lockstep/lockstep.d.ts +27 -0
  97. package/src/utils/version-lockstep/lockstep.js +46 -0
  98. package/src/utils/version-lockstep/lockstep.js.map +1 -0
  99. package/src/utils/versions.d.ts +9 -4
  100. package/src/utils/versions.js +24 -4
  101. package/src/utils/versions.js.map +1 -1
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { addDependenciesToPackageJson, getProjects, removeDependenciesFromPackageJson, updateProjectConfiguration } from "@nx/devkit";
5
+ import { formatFilesInSubtree } from "../../../utils/format.js";
6
+ import { TS_VERSIONS } from "../../../utils/versions.js";
7
+ /**
8
+ * One `shx` replaces the four single purpose filesystem CLIs the targets ran.
9
+ * Its `cp` is several times faster than `ncp` on a large tree, and a single
10
+ * dependency covers every operation the generators vend.
11
+ *
12
+ * `shx cp -R` follows POSIX `cp`, which nests the source inside an existing
13
+ * destination rather than merging into it, so a directory copy becomes
14
+ * `shx cp -R <src>/. <dst>`. That form also copies dotfiles, which a `<src>/*`
15
+ * glob would silently skip — `node_modules/.bin` depends on them. A file copy
16
+ * needs neither, so it stays a plain `shx cp`.
17
+ *
18
+ * Whether an `ncp` copied a file or a directory is not recoverable from the
19
+ * command, so each source is matched against the shapes the generators vend. A
20
+ * command that matches none of them has been edited by the user, and is left
21
+ * alone and reported: rewriting it with the wrong form would either nest a
22
+ * directory a level too deep or fail outright.
23
+ *
24
+ * A `cpy` becomes two commands rather than one joined by `&&`, which is a shell
25
+ * operator and so no more portable than the builtins `shx` replaces.
26
+ */ /** The four CLIs this replaces, dropped from the root manifest. */ const REPLACED_PACKAGES = [
27
+ 'ncp',
28
+ 'rimraf',
29
+ 'make-dir-cli',
30
+ 'cpy-cli'
31
+ ];
32
+ /**
33
+ * Sources the generators copy as a whole directory. Everything else they copy
34
+ * names a single file, so an unmatched source is neither and is left untouched.
35
+ *
36
+ * - a Python bundle (`bundle-arm`/`bundle-x86`), copied into a package or an
37
+ * image build context
38
+ * - a Python module tree, copied into a code package under its module name
39
+ * - the generated Server SDK, copied into a Smithy API's `src/generated`
40
+ * - `prisma` and Alembic `migrations`, copied into a migration bundle
41
+ */ const DIRECTORY_SOURCE = [
42
+ /(^|\/)bundle-(arm|x86)$/,
43
+ /(^|\/)build\/ssdk$/,
44
+ /(^|\/)prisma$/,
45
+ /(^|\/)migrations$/
46
+ ];
47
+ /**
48
+ * Files the generators copy whose name carries no extension, so the extension
49
+ * test below would otherwise read them as directories. A `Dockerfile` variant is
50
+ * renamed on the way in (`Dockerfile.migration` -> `Dockerfile`), so this matches
51
+ * the source's leading segment rather than the whole name.
52
+ */ const EXTENSIONLESS_FILE = /(^|\/)Dockerfile(\.[^/]+)?$/;
53
+ /**
54
+ * A Python code package copies the project's source root in under the module
55
+ * name, which is the same segment repeated — `.../src/my_agent my_agent`. The
56
+ * source is a directory whose name is the project's, so it is recognised by the
57
+ * destination repeating the source's last segment rather than by a fixed name.
58
+ */ const isModuleTreeCopy = (src, dst)=>{
59
+ const segment = src.split('/').pop();
60
+ return !!segment && !segment.includes('.') && dst.split('/').pop() === segment;
61
+ };
62
+ const isDirectorySource = (src, dst)=>!EXTENSIONLESS_FILE.test(src) && (DIRECTORY_SOURCE.some((p)=>p.test(src)) || isModuleTreeCopy(src, dst));
63
+ /**
64
+ * A copy naming a file on both sides, which every other vended `ncp` does. A
65
+ * name carrying an extension is a file — as is a `Dockerfile`, which does not.
66
+ */ const isFileCopy = (src, dst)=>{
67
+ const [s, d] = [
68
+ src.split('/').pop() ?? '',
69
+ dst.split('/').pop() ?? ''
70
+ ];
71
+ return (s.includes('.') || EXTENSIONLESS_FILE.test(src)) && (d.includes('.') || EXTENSIONLESS_FILE.test(dst));
72
+ };
73
+ const NCP = /^ncp\s+(\S+)\s+(\S+)$/;
74
+ const RIMRAF = /^rimraf\s+(\S+)$/;
75
+ const MAKE_DIR = /^make-dir\s+(\S+)$/;
76
+ /** The one shape `cpGlobToFile` vends, whose glob is quoted and dest renamed. */ const CPY = /^cpy\s+"([^"]+)"\s+(\S+)\s+--flat\s+--rename=(\S+)$/;
77
+ /**
78
+ * Rewrite one command into the commands that replace it, or return null to leave
79
+ * it alone. Anything already using `shx` is returned unchanged so a re-run is a
80
+ * no-op.
81
+ */ const rewrite = (command)=>{
82
+ const trimmed = command.trim();
83
+ const rimraf = RIMRAF.exec(trimmed);
84
+ if (rimraf) return [
85
+ `shx rm -rf ${rimraf[1]}`
86
+ ];
87
+ const makeDir = MAKE_DIR.exec(trimmed);
88
+ if (makeDir) return [
89
+ `shx mkdir -p ${makeDir[1]}`
90
+ ];
91
+ const cpy = CPY.exec(trimmed);
92
+ if (cpy) {
93
+ // `shx cp` writes through to a named destination file and fails if its
94
+ // parent is missing, so the destination directory is a step of its own.
95
+ return [
96
+ `shx mkdir -p ${cpy[2]}`,
97
+ `shx cp "${cpy[1]}" ${cpy[2]}/${cpy[3]}`
98
+ ];
99
+ }
100
+ const ncp = NCP.exec(trimmed);
101
+ if (ncp) {
102
+ const [, src, dst] = ncp;
103
+ if (isDirectorySource(src, dst)) return [
104
+ `shx cp -R ${src}/. ${dst}`
105
+ ];
106
+ if (isFileCopy(src, dst)) return [
107
+ `shx cp ${src} ${dst}`
108
+ ];
109
+ return null;
110
+ }
111
+ return null;
112
+ };
113
+ /** Whether a command is one this migration is responsible for rewriting. */ const isReplacedCommand = (command)=>/^(ncp|rimraf|make-dir|cpy)\s/.test(command.trim());
114
+ const divergedStep = (projectName, targetName, command)=>`${projectName}:${targetName}: left \`${command}\` untouched - it does not match a copy the generator produced, so whether it copies a file or a directory could not be determined. Replace it by hand with \`shx cp <src> <dst>\` for a file, or \`shx cp -R <src>/. <dst>\` for a directory (the trailing \`/.\` merges the contents instead of nesting the directory inside the destination).`;
115
+ /**
116
+ * Rewrite every command a target runs, in place. Commands are either strings or
117
+ * `{ command }` objects, and a target uses `command` for a single one.
118
+ *
119
+ * One command can become several, which are steps of the same operation and so
120
+ * must run in order — the target is pinned to `parallel: false` when that
121
+ * happens.
122
+ */ const migrateTarget = (projectName, targetName, target, nextSteps)=>{
123
+ const options = target.options;
124
+ if (!options) return false;
125
+ let changed = false;
126
+ let split = false;
127
+ /** The commands replacing one, or the original where it is left alone. */ const rewriteOne = (command)=>{
128
+ if (!isReplacedCommand(command)) return [
129
+ command
130
+ ];
131
+ const replacement = rewrite(command);
132
+ if (replacement === null) {
133
+ nextSteps.push(divergedStep(projectName, targetName, command.trim()));
134
+ return [
135
+ command
136
+ ];
137
+ }
138
+ changed = true;
139
+ if (replacement.length > 1) split = true;
140
+ return replacement;
141
+ };
142
+ if (typeof options.command === 'string') {
143
+ const replacement = rewriteOne(options.command);
144
+ if (replacement.length > 1) {
145
+ // `commands` is the only option that runs more than one.
146
+ delete options.command;
147
+ options.commands = replacement;
148
+ } else {
149
+ options.command = replacement[0];
150
+ }
151
+ } else if (Array.isArray(options.commands)) {
152
+ options.commands = options.commands.flatMap((entry)=>{
153
+ if (typeof entry === 'string') return rewriteOne(entry);
154
+ if (entry && typeof entry === 'object' && typeof entry.command === 'string') {
155
+ const e = entry;
156
+ return rewriteOne(e.command).map((command)=>({
157
+ ...e,
158
+ command
159
+ }));
160
+ }
161
+ return [
162
+ entry
163
+ ];
164
+ });
165
+ }
166
+ if (split) options.parallel = false;
167
+ return changed;
168
+ };
169
+ /** Every command every target in the workspace runs. */ const allCommands = (tree)=>{
170
+ const commands = [];
171
+ for (const [, project] of getProjects(tree)){
172
+ for (const target of Object.values(project.targets ?? {})){
173
+ const options = target.options;
174
+ if (typeof options?.command === 'string') commands.push(options.command);
175
+ if (Array.isArray(options?.commands)) {
176
+ for (const entry of options.commands){
177
+ if (typeof entry === 'string') commands.push(entry);
178
+ else if (typeof entry?.command === 'string') commands.push(entry.command);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ return commands;
184
+ };
185
+ /** The CLI a package provides, as the rewritten commands would name it. */ const CLI_NAME = {
186
+ ncp: 'ncp',
187
+ rimraf: 'rimraf',
188
+ 'make-dir-cli': 'make-dir',
189
+ 'cpy-cli': 'cpy'
190
+ };
191
+ export default async function migration(tree) {
192
+ const nextSteps = [];
193
+ for (const [projectName, project] of getProjects(tree)){
194
+ let changed = false;
195
+ for (const [targetName, target] of Object.entries(project.targets ?? {})){
196
+ changed = migrateTarget(projectName, targetName, target, nextSteps) || changed;
197
+ }
198
+ if (changed) {
199
+ updateProjectConfiguration(tree, projectName, project);
200
+ }
201
+ }
202
+ addDependenciesToPackageJson(tree, {}, {
203
+ shx: TS_VERSIONS.shx
204
+ });
205
+ // Only the CLIs nothing runs any more: a command this migration declined to
206
+ // rewrite, or one the user added, still needs the package it calls.
207
+ const commands = allCommands(tree);
208
+ const unused = REPLACED_PACKAGES.filter((pkg)=>!commands.some((c)=>new RegExp(`(^|[^\\w-])${CLI_NAME[pkg]}\\s`).test(c)));
209
+ removeDependenciesFromPackageJson(tree, unused, unused);
210
+ await formatFilesInSubtree(tree);
211
+ return {
212
+ nextSteps
213
+ };
214
+ }
215
+
216
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/shx-fs-commands/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n addDependenciesToPackageJson,\n getProjects,\n type MigrationReturnObject,\n type ProjectConfiguration,\n removeDependenciesFromPackageJson,\n type TargetConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { TS_VERSIONS } from '../../../utils/versions.js';\n\n/**\n * One `shx` replaces the four single purpose filesystem CLIs the targets ran.\n * Its `cp` is several times faster than `ncp` on a large tree, and a single\n * dependency covers every operation the generators vend.\n *\n * `shx cp -R` follows POSIX `cp`, which nests the source inside an existing\n * destination rather than merging into it, so a directory copy becomes\n * `shx cp -R <src>/. <dst>`. That form also copies dotfiles, which a `<src>/*`\n * glob would silently skip — `node_modules/.bin` depends on them. A file copy\n * needs neither, so it stays a plain `shx cp`.\n *\n * Whether an `ncp` copied a file or a directory is not recoverable from the\n * command, so each source is matched against the shapes the generators vend. A\n * command that matches none of them has been edited by the user, and is left\n * alone and reported: rewriting it with the wrong form would either nest a\n * directory a level too deep or fail outright.\n *\n * A `cpy` becomes two commands rather than one joined by `&&`, which is a shell\n * operator and so no more portable than the builtins `shx` replaces.\n */\n\n/** The four CLIs this replaces, dropped from the root manifest. */\nconst REPLACED_PACKAGES = ['ncp', 'rimraf', 'make-dir-cli', 'cpy-cli'];\n\n/**\n * Sources the generators copy as a whole directory. Everything else they copy\n * names a single file, so an unmatched source is neither and is left untouched.\n *\n * - a Python bundle (`bundle-arm`/`bundle-x86`), copied into a package or an\n * image build context\n * - a Python module tree, copied into a code package under its module name\n * - the generated Server SDK, copied into a Smithy API's `src/generated`\n * - `prisma` and Alembic `migrations`, copied into a migration bundle\n */\nconst DIRECTORY_SOURCE = [\n /(^|\\/)bundle-(arm|x86)$/,\n /(^|\\/)build\\/ssdk$/,\n /(^|\\/)prisma$/,\n /(^|\\/)migrations$/,\n];\n\n/**\n * Files the generators copy whose name carries no extension, so the extension\n * test below would otherwise read them as directories. A `Dockerfile` variant is\n * renamed on the way in (`Dockerfile.migration` -> `Dockerfile`), so this matches\n * the source's leading segment rather than the whole name.\n */\nconst EXTENSIONLESS_FILE = /(^|\\/)Dockerfile(\\.[^/]+)?$/;\n\n/**\n * A Python code package copies the project's source root in under the module\n * name, which is the same segment repeated — `.../src/my_agent my_agent`. The\n * source is a directory whose name is the project's, so it is recognised by the\n * destination repeating the source's last segment rather than by a fixed name.\n */\nconst isModuleTreeCopy = (src: string, dst: string): boolean => {\n const segment = src.split('/').pop();\n return (\n !!segment && !segment.includes('.') && dst.split('/').pop() === segment\n );\n};\n\nconst isDirectorySource = (src: string, dst: string): boolean =>\n !EXTENSIONLESS_FILE.test(src) &&\n (DIRECTORY_SOURCE.some((p) => p.test(src)) || isModuleTreeCopy(src, dst));\n\n/**\n * A copy naming a file on both sides, which every other vended `ncp` does. A\n * name carrying an extension is a file — as is a `Dockerfile`, which does not.\n */\nconst isFileCopy = (src: string, dst: string): boolean => {\n const [s, d] = [src.split('/').pop() ?? '', dst.split('/').pop() ?? ''];\n return (\n (s.includes('.') || EXTENSIONLESS_FILE.test(src)) &&\n (d.includes('.') || EXTENSIONLESS_FILE.test(dst))\n );\n};\n\nconst NCP = /^ncp\\s+(\\S+)\\s+(\\S+)$/;\nconst RIMRAF = /^rimraf\\s+(\\S+)$/;\nconst MAKE_DIR = /^make-dir\\s+(\\S+)$/;\n/** The one shape `cpGlobToFile` vends, whose glob is quoted and dest renamed. */\nconst CPY = /^cpy\\s+\"([^\"]+)\"\\s+(\\S+)\\s+--flat\\s+--rename=(\\S+)$/;\n\n/**\n * Rewrite one command into the commands that replace it, or return null to leave\n * it alone. Anything already using `shx` is returned unchanged so a re-run is a\n * no-op.\n */\nconst rewrite = (command: string): string[] | null => {\n const trimmed = command.trim();\n\n const rimraf = RIMRAF.exec(trimmed);\n if (rimraf) return [`shx rm -rf ${rimraf[1]}`];\n\n const makeDir = MAKE_DIR.exec(trimmed);\n if (makeDir) return [`shx mkdir -p ${makeDir[1]}`];\n\n const cpy = CPY.exec(trimmed);\n if (cpy) {\n // `shx cp` writes through to a named destination file and fails if its\n // parent is missing, so the destination directory is a step of its own.\n return [`shx mkdir -p ${cpy[2]}`, `shx cp \"${cpy[1]}\" ${cpy[2]}/${cpy[3]}`];\n }\n\n const ncp = NCP.exec(trimmed);\n if (ncp) {\n const [, src, dst] = ncp;\n if (isDirectorySource(src, dst)) return [`shx cp -R ${src}/. ${dst}`];\n if (isFileCopy(src, dst)) return [`shx cp ${src} ${dst}`];\n return null;\n }\n\n return null;\n};\n\n/** Whether a command is one this migration is responsible for rewriting. */\nconst isReplacedCommand = (command: string): boolean =>\n /^(ncp|rimraf|make-dir|cpy)\\s/.test(command.trim());\n\nconst divergedStep = (\n projectName: string,\n targetName: string,\n command: string,\n) =>\n `${projectName}:${targetName}: left \\`${command}\\` untouched - it does not match a copy the generator produced, so whether it copies a file or a directory could not be determined. Replace it by hand with \\`shx cp <src> <dst>\\` for a file, or \\`shx cp -R <src>/. <dst>\\` for a directory (the trailing \\`/.\\` merges the contents instead of nesting the directory inside the destination).`;\n\n/**\n * Rewrite every command a target runs, in place. Commands are either strings or\n * `{ command }` objects, and a target uses `command` for a single one.\n *\n * One command can become several, which are steps of the same operation and so\n * must run in order — the target is pinned to `parallel: false` when that\n * happens.\n */\nconst migrateTarget = (\n projectName: string,\n targetName: string,\n target: TargetConfiguration,\n nextSteps: string[],\n): boolean => {\n const options = target.options as\n | { command?: unknown; commands?: unknown; parallel?: unknown }\n | undefined;\n if (!options) return false;\n\n let changed = false;\n let split = false;\n\n /** The commands replacing one, or the original where it is left alone. */\n const rewriteOne = (command: string): string[] => {\n if (!isReplacedCommand(command)) return [command];\n const replacement = rewrite(command);\n if (replacement === null) {\n nextSteps.push(divergedStep(projectName, targetName, command.trim()));\n return [command];\n }\n changed = true;\n if (replacement.length > 1) split = true;\n return replacement;\n };\n\n if (typeof options.command === 'string') {\n const replacement = rewriteOne(options.command);\n if (replacement.length > 1) {\n // `commands` is the only option that runs more than one.\n delete options.command;\n options.commands = replacement;\n } else {\n options.command = replacement[0];\n }\n } else if (Array.isArray(options.commands)) {\n options.commands = options.commands.flatMap((entry) => {\n if (typeof entry === 'string') return rewriteOne(entry);\n if (\n entry &&\n typeof entry === 'object' &&\n typeof (entry as { command?: unknown }).command === 'string'\n ) {\n const e = entry as { command: string };\n return rewriteOne(e.command).map((command) => ({ ...e, command }));\n }\n return [entry];\n });\n }\n\n if (split) options.parallel = false;\n\n return changed;\n};\n\n/** Every command every target in the workspace runs. */\nconst allCommands = (tree: Tree): string[] => {\n const commands: string[] = [];\n for (const [, project] of getProjects(tree)) {\n for (const target of Object.values(project.targets ?? {})) {\n const options = target.options as\n | { command?: unknown; commands?: unknown }\n | undefined;\n if (typeof options?.command === 'string') commands.push(options.command);\n if (Array.isArray(options?.commands)) {\n for (const entry of options.commands) {\n if (typeof entry === 'string') commands.push(entry);\n else if (\n typeof (entry as { command?: unknown })?.command === 'string'\n )\n commands.push((entry as { command: string }).command);\n }\n }\n }\n }\n return commands;\n};\n\n/** The CLI a package provides, as the rewritten commands would name it. */\nconst CLI_NAME: Record<string, string> = {\n ncp: 'ncp',\n rimraf: 'rimraf',\n 'make-dir-cli': 'make-dir',\n 'cpy-cli': 'cpy',\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n let changed = false;\n\n for (const [targetName, target] of Object.entries(project.targets ?? {})) {\n changed =\n migrateTarget(projectName, targetName, target, nextSteps) || changed;\n }\n\n if (changed) {\n updateProjectConfiguration(\n tree,\n projectName,\n project as ProjectConfiguration,\n );\n }\n }\n\n addDependenciesToPackageJson(tree, {}, { shx: TS_VERSIONS.shx });\n\n // Only the CLIs nothing runs any more: a command this migration declined to\n // rewrite, or one the user added, still needs the package it calls.\n const commands = allCommands(tree);\n const unused = REPLACED_PACKAGES.filter(\n (pkg) =>\n !commands.some((c) =>\n new RegExp(`(^|[^\\\\w-])${CLI_NAME[pkg]}\\\\s`).test(c),\n ),\n );\n removeDependenciesFromPackageJson(tree, unused, unused);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["addDependenciesToPackageJson","getProjects","removeDependenciesFromPackageJson","updateProjectConfiguration","formatFilesInSubtree","TS_VERSIONS","REPLACED_PACKAGES","DIRECTORY_SOURCE","EXTENSIONLESS_FILE","isModuleTreeCopy","src","dst","segment","split","pop","includes","isDirectorySource","test","some","p","isFileCopy","s","d","NCP","RIMRAF","MAKE_DIR","CPY","rewrite","command","trimmed","trim","rimraf","exec","makeDir","cpy","ncp","isReplacedCommand","divergedStep","projectName","targetName","migrateTarget","target","nextSteps","options","changed","rewriteOne","replacement","push","length","commands","Array","isArray","flatMap","entry","e","map","parallel","allCommands","tree","project","Object","values","targets","CLI_NAME","migration","entries","shx","unused","filter","pkg","c","RegExp"],"mappings":"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"}
@@ -332,7 +332,7 @@ export type TestNullable200Response = {
332
332
  nullableInteger?: number | null;
333
333
  nullableBoolean?: boolean | null;
334
334
  nullableArray?: Array<string> | null;
335
- nullableObject?: TestNullable200ResponseNullableObject;
335
+ nullableObject?: TestNullable200ResponseNullableObject | null;
336
336
  };
337
337
  export type TestNullable200ResponseNullableObject = null | {
338
338
  key?: string;
@@ -343,7 +343,7 @@ export type TestNullableRequestContent = {
343
343
  nullableInteger?: number | null;
344
344
  nullableBoolean?: boolean | null;
345
345
  nullableArray?: Array<string> | null;
346
- nullableObject?: TestNullableRequestContentNullableObject;
346
+ nullableObject?: TestNullableRequestContentNullableObject | null;
347
347
  objectWithNullableProps?: TestNullableRequestContentObjectWithNullableProps;
348
348
  };
349
349
  export type TestNullableRequestContentNullableObject = null | {
@@ -355,7 +355,7 @@ export type TestNullableRequestContentObjectWithNullableProps = {
355
355
  nullableInteger?: number | null;
356
356
  nullableBoolean?: boolean | null;
357
357
  nullableArray?: Array<string> | null;
358
- nullableObject?: TestNullableRequestContentObjectWithNullablePropsNullableObject;
358
+ nullableObject?: TestNullableRequestContentObjectWithNullablePropsNullableObject | null;
359
359
  };
360
360
  export type TestNullableRequestContentObjectWithNullablePropsNullableObject =
361
361
  null | {
@@ -538,9 +538,11 @@ export class $IO {
538
538
  ? {}
539
539
  : {
540
540
  nullableObject:
541
- $IO.TestNullable200ResponseNullableObject.fromJson(
542
- json['nullableObject'],
543
- ),
541
+ json['nullableObject'] === null
542
+ ? null
543
+ : $IO.TestNullable200ResponseNullableObject.fromJson(
544
+ json['nullableObject'],
545
+ ),
544
546
  }),
545
547
  };
546
548
  },
@@ -666,9 +668,11 @@ export class $IO {
666
668
  ? {}
667
669
  : {
668
670
  nullableObject:
669
- $IO.TestNullableRequestContentNullableObject.fromJson(
670
- json['nullableObject'],
671
- ),
671
+ json['nullableObject'] === null
672
+ ? null
673
+ : $IO.TestNullableRequestContentNullableObject.fromJson(
674
+ json['nullableObject'],
675
+ ),
672
676
  }),
673
677
  ...(json['objectWithNullableProps'] === undefined
674
678
  ? {}
@@ -796,9 +800,11 @@ export class $IO {
796
800
  ? {}
797
801
  : {
798
802
  nullableObject:
799
- $IO.TestNullableRequestContentObjectWithNullablePropsNullableObject.fromJson(
800
- json['nullableObject'],
801
- ),
803
+ json['nullableObject'] === null
804
+ ? null
805
+ : $IO.TestNullableRequestContentObjectWithNullablePropsNullableObject.fromJson(
806
+ json['nullableObject'],
807
+ ),
802
808
  }),
803
809
  };
804
810
  },
@@ -4262,7 +4262,10 @@ export class $IO {
4262
4262
  ...(json['wrapper'] === undefined
4263
4263
  ? {}
4264
4264
  : {
4265
- wrapper: $IO.ThingWrapper.fromJson(json['wrapper']),
4265
+ wrapper:
4266
+ json['wrapper'] === null
4267
+ ? null
4268
+ : $IO.ThingWrapper.fromJson(json['wrapper']),
4266
4269
  }),
4267
4270
  };
4268
4271
  },
@@ -4447,7 +4450,7 @@ export class TestApi {
4447
4450
 
4448
4451
  exports[`openApiTsClientGenerator - edge cases > rewrites a const declared inside a nullable (multi-type) object > types.gen.ts 1`] = `
4449
4452
  "export type Thing = {
4450
- wrapper?: ThingWrapper;
4453
+ wrapper?: ThingWrapper | null;
4451
4454
  };
4452
4455
  export type ThingWrapper = null | {
4453
4456
  kind?: ThingWrapperKind;
@@ -98,6 +98,12 @@ export interface Model {
98
98
  isReadOnly: boolean;
99
99
  /** Whether the property/parameter is required. */
100
100
  isRequired: boolean;
101
+ /**
102
+ * The `required` list this schema declared, kept even when it declares none of
103
+ * the properties it names — an `allOf` member may raise a sibling branch's
104
+ * property to mandatory, which is not the same as constraining nothing.
105
+ */
106
+ declaredRequired?: string[];
101
107
  /** Whether an array enforces uniqueness (rendered as a `Set`). */
102
108
  uniqueItems?: boolean;
103
109
  /** For arrays/dictionaries, the model describing the element/value type. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/open-api/utils/codegen-data/types.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\n/**\n * The kind of a {@link Model}, describing how it should be rendered.\n *\n * - `interface` — an object type with named properties\n * - `enum` — a set of string/number literal values\n * - `array` — a list whose element type is described by `link`\n * - `tuple` — a fixed-length array (`prefixItems`) whose element types are the\n * `properties`, in order\n * - `dictionary` — a map whose value type is described by `link`\n * - `reference` — a reference to another named model (`type` is its name)\n * - `generic` — a primitive (string/number/boolean/null/void/binary/any)\n * - `one-of` / `any-of` / `all-of` — a composite of other schemas\n */\nexport type ModelExport =\n | 'interface'\n | 'enum'\n | 'array'\n | 'tuple'\n | 'dictionary'\n | 'reference'\n | 'generic'\n | 'one-of'\n | 'any-of'\n | 'all-of';\n\n/**\n * Where a parameter-like {@link Model} appears in a request/response.\n * Empty string is used for models that are not parameters (schemas/properties).\n */\nexport type ModelIn =\n | ''\n | 'query'\n | 'path'\n | 'header'\n | 'cookie'\n | 'body'\n | 'response';\n\n/**\n * How array/object query & header parameters are serialised on the wire.\n * `deepObject` explodes an object into `key[prop]=value` query pairs.\n */\nexport type CollectionFormat = 'multi' | 'csv' | 'ssv' | 'pipes' | 'deepObject';\n\n/** A single enum member value. */\nexport interface EnumMember {\n value: string | number | boolean;\n}\n\n/**\n * A discriminated-composite mapping entry: a discriminator value and the name\n * of the composed model it selects.\n */\nexport interface DiscriminatorMapping {\n /** The discriminator property value (e.g. `\"cat\"`). */\n value: string;\n /** The name of the composed model this value selects (e.g. `\"Cat\"`). */\n modelName: string;\n}\n\n/**\n * The discriminator of a composite (`oneOf`/`anyOf`) or an inheritance base\n * (an `object` schema whose subtypes `allOf`-compose it), used to marshal\n * directly to the matching branch/subtype rather than merging every branch or\n * dropping subtype-only fields.\n */\nexport interface Discriminator {\n /** The wire property name carrying the discriminator value. */\n propertyName: string;\n /** The TypeScript property name (resolved during augmentation). */\n typescriptPropertyName?: string;\n /** The value → composed-model mapping. */\n mapping: DiscriminatorMapping[];\n /**\n * True when this discriminator sits on an inheritance base (an `object`\n * schema selected by `allOf`-composing subtypes) rather than a `oneOf`/\n * `anyOf` composite. The base marshals via dispatch to its subtypes, but its\n * subtypes compose the base's own (non-dispatching) body — so the base emits\n * a separate non-dispatching marshaller to break the recursion.\n */\n isBase?: boolean;\n}\n\n/** A pattern-property entry: a regex pattern and the model for its values. */\nexport interface PatternPropertyModel {\n pattern: string;\n model: Model;\n}\n\n/**\n * A model represents an OpenAPI schema, or something derived from one (a\n * property, parameter, or response). It is produced by the parser\n * (`../parser.ts`) and progressively augmented by `../codegen-data.ts` with\n * language-specific and code-generation fields before being handed to the\n * templates. Augmentation fields are optional as they are absent on raw parser\n * output.\n */\nexport interface Model {\n /** The schema, property, or parameter name. */\n name: string;\n /** How this model should be rendered. */\n export: ModelExport;\n /**\n * The model's type: a primitive name (`string`, `number`, `boolean`, `null`,\n * `void`, `binary`, `any`), `unknown`, or the name of a referenced model.\n */\n type: string;\n /** The OpenAPI `format` (e.g. `int32`, `date-time`, `binary`), if any. */\n format?: string;\n /** Description, used to render doc comments. */\n description: string | null;\n /** Whether the schema is marked deprecated. */\n deprecated: boolean;\n /** Whether the value may be null. */\n isNullable: boolean;\n /** Whether the property is read-only. */\n isReadOnly: boolean;\n /** Whether the property/parameter is required. */\n isRequired: boolean;\n /** Whether an array enforces uniqueness (rendered as a `Set`). */\n uniqueItems?: boolean;\n /** For arrays/dictionaries, the model describing the element/value type. */\n link?: Model | null;\n /** Child properties (for interfaces) or members (for composites). */\n properties: Model[];\n /** Enum members, when `export === 'enum'`. */\n enum: EnumMember[];\n /** Names of other models this model references (for import generation). */\n imports: string[];\n\n /** Where a parameter appears; `''` for non-parameter models. */\n in: ModelIn;\n /** The source property name for a parameter (body parameters use `body`). */\n prop?: string;\n /** The chosen request/response media type, for body params and responses. */\n mediaType?: string | null;\n /**\n * Per-part content types declared by the request body `encoding` object\n * (multipart bodies), keyed by property name.\n */\n partContentTypes?: { [prop: string]: string };\n /** All acceptable media types (request body / response). */\n mediaTypes?: string[];\n /** The response status code, for response models. */\n code?: number | string;\n\n /**\n * For a discriminated subtype's discriminator property: the rendered literal\n * TypeScript type (e.g. `'cat'`, or `'cat' | 'kitten'` when several values\n * map to one subtype) that makes the union a true tagged union. Absent when\n * the subtype's tag can't be pinned to a literal (e.g. it appears in\n * multiple unions with different values).\n */\n discriminatorValue?: string;\n\n /** The raw OpenAPI `type` (used to distinguish integer from number). */\n openapiType?: string | string[];\n /** Vendor extensions (`x-*`) copied from the schema. */\n vendorExtensions?: VendorExtensions;\n /** True when the schema has additionalProperties. */\n hasAdditionalProperties?: boolean;\n /** The model describing additionalProperties values. */\n additionalPropertiesModel?: Model;\n /** True when the schema has patternProperties. */\n hasPatternProperties?: boolean;\n /** The models describing each pattern's values. */\n patternPropertiesModels?: PatternPropertyModel[];\n\n /** For composites: the referenced (object) models composed together. */\n composedModels?: Model[];\n /** For composites: the primitive/enum/array members composed together. */\n composedPrimitives?: Model[];\n /** For discriminated one-of/any-of composites: the discriminator metadata. */\n discriminator?: Discriminator;\n\n /** The TypeScript identifier for this model/property. */\n typescriptName?: string;\n /** The rendered TypeScript type. */\n typescriptType?: string;\n /** The Python identifier for this model/property. */\n pythonName?: string;\n /** The rendered Python type. */\n pythonType?: string;\n /** snake_case model name (Python). */\n nameSnakeCase?: string;\n /** True when the model is a renderable primitive (not composite/collection). */\n isPrimitive?: boolean;\n /** True when the schema declares an enum. */\n isEnum?: boolean;\n\n /** True for JSON-lines streaming responses. */\n isJsonlStreaming?: boolean;\n /** The model describing each streamed item. */\n itemSchemaModel?: Model;\n\n /** Collection serialisation format for array query/header parameters. */\n collectionFormat?: CollectionFormat;\n /** Path parameter serialization style, when not the default `simple`. */\n pathStyle?: 'matrix' | 'label';\n /** Path parameter explode flag (used with `pathStyle`). */\n pathExplode?: boolean;\n /** Query parameter `allowReserved`: reserved characters are not encoded. */\n allowReserved?: boolean;\n}\n\n/** Vendor extension bag (`x-*` keys copied from a schema/operation/spec). */\nexport type VendorExtensions = { [key: string]: unknown };\n\n/**\n * An operation represents a single OpenAPI path + method. Produced by the\n * parser and augmented by `../codegen-data.ts`.\n */\nexport interface Operation {\n id: string;\n name: string;\n method: string;\n path: string;\n description: string | null;\n tags?: string[] | null;\n deprecated: boolean;\n imports: string[];\n parameters: Model[];\n /** The request body parameter (same object reference as in `parameters`). */\n parametersBody?: Model | null;\n responses: Model[];\n\n /** Deduplicated operation name used throughout generated code. */\n uniqueName?: string;\n /** Dot-notation name (tag-qualified), used for metadata keys. */\n dotNotationName?: string;\n operationIdPascalCase?: string;\n operationIdSnakeCase?: string;\n /** The generated request type name (e.g. `FooRequest`). */\n requestTypeName?: string;\n\n /** The response model used as the operation's result. */\n result?: Model;\n\n /** The explicit body parameter when the body is not inlined. */\n explicitRequestBodyParameter?: Model;\n\n vendorExtensions?: VendorExtensions;\n isMutation?: boolean;\n isQuery?: boolean;\n isStreaming?: boolean;\n isInfiniteQuery?: boolean;\n /** The cursor parameter, for infinite queries. */\n infiniteQueryCursorProperty?: Model;\n}\n\n/**\n * A service groups operations. Currently the parser produces a single\n * `Default` service; operations are re-grouped by tag downstream.\n */\nexport interface Service {\n name: string;\n operations: Operation[];\n imports: string[];\n\n /** All model names the service (API client) needs to import. */\n modelImports?: string[];\n className?: string;\n nameSnakeCase?: string;\n}\n\n/**\n * The initial data structure produced by the OpenAPI parser.\n */\nexport interface ClientData {\n models: Model[];\n services: Service[];\n}\n\n/** Models indexed by name, for reference resolution during augmentation. */\nexport type ModelsByName = { [name: string]: Model };\n\n/** Index a list of models by name. */\nexport const indexModelsByName = (models: Model[]): ModelsByName =>\n Object.fromEntries(models.map((m) => [m.name, m]));\n\n/** The single service the parser emits; operations are re-grouped by tag. */\nexport const DEFAULT_SERVICE_NAME = 'Default';\n\n/**\n * The full data structure handed to the code generation templates.\n */\nexport interface CodeGenData extends ClientData {\n className: string;\n info: OpenAPIV3.InfoObject | OpenAPIV3_1.InfoObject;\n allOperations: Operation[];\n operationsByTag: { [tag: string]: Operation[] };\n untaggedOperations: Operation[];\n vendorExtensions: VendorExtensions;\n}\n\n/**\n * Create a {@link Model} with the default fields the parser always emits.\n * Callers override the fields relevant to their specific schema.\n */\nexport const createModel = (overrides: Partial<Model> = {}): Model => ({\n name: '',\n export: 'interface',\n type: 'unknown',\n description: null,\n deprecated: false,\n isNullable: false,\n isReadOnly: false,\n isRequired: false,\n link: null,\n properties: [],\n enum: [],\n imports: [],\n in: '',\n ...overrides,\n});\n\n// Model types which indicate it is composed (ie inherits/mixin's another schema)\nexport const COMPOSED_SCHEMA_TYPES = new Set<ModelExport>([\n 'one-of',\n 'any-of',\n 'all-of',\n]);\nexport const COLLECTION_TYPES = new Set<ModelExport>(['array', 'dictionary']);\nexport const PRIMITIVE_TYPES = new Set([\n 'string',\n 'integer',\n 'number',\n 'boolean',\n 'null',\n 'any',\n 'binary',\n 'void',\n]);\n\n/**\n * Content types that indicate JSON Lines streaming (OpenAPI 3.2)\n */\nexport const STREAMING_CONTENT_TYPES = new Set([\n 'application/jsonl',\n 'application/x-ndjson',\n]);\n\n/**\n * Vendor extensions which are used to customise generated code\n */\nexport const VENDOR_EXTENSIONS = {\n /**\n * Set to 'true' to indicate this is a streaming API\n */\n STREAMING: 'x-streaming',\n /**\n * Set to true to indicate this is a mutation, regardless of its HTTP method\n */\n MUTATION: 'x-mutation',\n /**\n * Set to true to indicate this is a query, regardless of its HTTP method\n */\n QUERY: 'x-query',\n /**\n * Set to the name of the input property used as the cursor for pagination if\n * the API accepts a cursor that is not named 'cursor'.\n * This can also be set to false to override behaviour and indicate this is not\n * a paginated API.\n * Used for tanstack infinite query hooks.\n */\n CURSOR: 'x-cursor',\n} as const;\n"],"names":["indexModelsByName","models","Object","fromEntries","map","m","name","DEFAULT_SERVICE_NAME","createModel","overrides","export","type","description","deprecated","isNullable","isReadOnly","isRequired","link","properties","enum","imports","in","COMPOSED_SCHEMA_TYPES","Set","COLLECTION_TYPES","PRIMITIVE_TYPES","STREAMING_CONTENT_TYPES","VENDOR_EXTENSIONS","STREAMING","MUTATION","QUERY","CURSOR"],"mappings":"AAAA;;;CAGC,GAuRD,oCAAoC,GACpC,OAAO,MAAMA,oBAAoB,CAACC,SAChCC,OAAOC,WAAW,CAACF,OAAOG,GAAG,CAAC,CAACC,IAAM;YAACA,EAAEC,IAAI;YAAED;SAAE,GAAG;AAErD,2EAA2E,GAC3E,OAAO,MAAME,uBAAuB,UAAU;AAc9C;;;CAGC,GACD,OAAO,MAAMC,cAAc,CAACC,YAA4B,CAAC,CAAC,GAAa,CAAA;QACrEH,MAAM;QACNI,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,MAAM;QACNC,YAAY,EAAE;QACdC,MAAM,EAAE;QACRC,SAAS,EAAE;QACXC,IAAI;QACJ,GAAGZ,SAAS;IACd,CAAA,EAAG;AAEH,iFAAiF;AACjF,OAAO,MAAMa,wBAAwB,IAAIC,IAAiB;IACxD;IACA;IACA;CACD,EAAE;AACH,OAAO,MAAMC,mBAAmB,IAAID,IAAiB;IAAC;IAAS;CAAa,EAAE;AAC9E,OAAO,MAAME,kBAAkB,IAAIF,IAAI;IACrC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAE;AAEH;;CAEC,GACD,OAAO,MAAMG,0BAA0B,IAAIH,IAAI;IAC7C;IACA;CACD,EAAE;AAEH;;CAEC,GACD,OAAO,MAAMI,oBAAoB;IAC/B;;GAEC,GACDC,WAAW;IACX;;GAEC,GACDC,UAAU;IACV;;GAEC,GACDC,OAAO;IACP;;;;;;GAMC,GACDC,QAAQ;AACV,EAAW"}
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/open-api/utils/codegen-data/types.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\n/**\n * The kind of a {@link Model}, describing how it should be rendered.\n *\n * - `interface` — an object type with named properties\n * - `enum` — a set of string/number literal values\n * - `array` — a list whose element type is described by `link`\n * - `tuple` — a fixed-length array (`prefixItems`) whose element types are the\n * `properties`, in order\n * - `dictionary` — a map whose value type is described by `link`\n * - `reference` — a reference to another named model (`type` is its name)\n * - `generic` — a primitive (string/number/boolean/null/void/binary/any)\n * - `one-of` / `any-of` / `all-of` — a composite of other schemas\n */\nexport type ModelExport =\n | 'interface'\n | 'enum'\n | 'array'\n | 'tuple'\n | 'dictionary'\n | 'reference'\n | 'generic'\n | 'one-of'\n | 'any-of'\n | 'all-of';\n\n/**\n * Where a parameter-like {@link Model} appears in a request/response.\n * Empty string is used for models that are not parameters (schemas/properties).\n */\nexport type ModelIn =\n | ''\n | 'query'\n | 'path'\n | 'header'\n | 'cookie'\n | 'body'\n | 'response';\n\n/**\n * How array/object query & header parameters are serialised on the wire.\n * `deepObject` explodes an object into `key[prop]=value` query pairs.\n */\nexport type CollectionFormat = 'multi' | 'csv' | 'ssv' | 'pipes' | 'deepObject';\n\n/** A single enum member value. */\nexport interface EnumMember {\n value: string | number | boolean;\n}\n\n/**\n * A discriminated-composite mapping entry: a discriminator value and the name\n * of the composed model it selects.\n */\nexport interface DiscriminatorMapping {\n /** The discriminator property value (e.g. `\"cat\"`). */\n value: string;\n /** The name of the composed model this value selects (e.g. `\"Cat\"`). */\n modelName: string;\n}\n\n/**\n * The discriminator of a composite (`oneOf`/`anyOf`) or an inheritance base\n * (an `object` schema whose subtypes `allOf`-compose it), used to marshal\n * directly to the matching branch/subtype rather than merging every branch or\n * dropping subtype-only fields.\n */\nexport interface Discriminator {\n /** The wire property name carrying the discriminator value. */\n propertyName: string;\n /** The TypeScript property name (resolved during augmentation). */\n typescriptPropertyName?: string;\n /** The value → composed-model mapping. */\n mapping: DiscriminatorMapping[];\n /**\n * True when this discriminator sits on an inheritance base (an `object`\n * schema selected by `allOf`-composing subtypes) rather than a `oneOf`/\n * `anyOf` composite. The base marshals via dispatch to its subtypes, but its\n * subtypes compose the base's own (non-dispatching) body — so the base emits\n * a separate non-dispatching marshaller to break the recursion.\n */\n isBase?: boolean;\n}\n\n/** A pattern-property entry: a regex pattern and the model for its values. */\nexport interface PatternPropertyModel {\n pattern: string;\n model: Model;\n}\n\n/**\n * A model represents an OpenAPI schema, or something derived from one (a\n * property, parameter, or response). It is produced by the parser\n * (`../parser.ts`) and progressively augmented by `../codegen-data.ts` with\n * language-specific and code-generation fields before being handed to the\n * templates. Augmentation fields are optional as they are absent on raw parser\n * output.\n */\nexport interface Model {\n /** The schema, property, or parameter name. */\n name: string;\n /** How this model should be rendered. */\n export: ModelExport;\n /**\n * The model's type: a primitive name (`string`, `number`, `boolean`, `null`,\n * `void`, `binary`, `any`), `unknown`, or the name of a referenced model.\n */\n type: string;\n /** The OpenAPI `format` (e.g. `int32`, `date-time`, `binary`), if any. */\n format?: string;\n /** Description, used to render doc comments. */\n description: string | null;\n /** Whether the schema is marked deprecated. */\n deprecated: boolean;\n /** Whether the value may be null. */\n isNullable: boolean;\n /** Whether the property is read-only. */\n isReadOnly: boolean;\n /** Whether the property/parameter is required. */\n isRequired: boolean;\n /**\n * The `required` list this schema declared, kept even when it declares none of\n * the properties it names — an `allOf` member may raise a sibling branch's\n * property to mandatory, which is not the same as constraining nothing.\n */\n declaredRequired?: string[];\n /** Whether an array enforces uniqueness (rendered as a `Set`). */\n uniqueItems?: boolean;\n /** For arrays/dictionaries, the model describing the element/value type. */\n link?: Model | null;\n /** Child properties (for interfaces) or members (for composites). */\n properties: Model[];\n /** Enum members, when `export === 'enum'`. */\n enum: EnumMember[];\n /** Names of other models this model references (for import generation). */\n imports: string[];\n\n /** Where a parameter appears; `''` for non-parameter models. */\n in: ModelIn;\n /** The source property name for a parameter (body parameters use `body`). */\n prop?: string;\n /** The chosen request/response media type, for body params and responses. */\n mediaType?: string | null;\n /**\n * Per-part content types declared by the request body `encoding` object\n * (multipart bodies), keyed by property name.\n */\n partContentTypes?: { [prop: string]: string };\n /** All acceptable media types (request body / response). */\n mediaTypes?: string[];\n /** The response status code, for response models. */\n code?: number | string;\n\n /**\n * For a discriminated subtype's discriminator property: the rendered literal\n * TypeScript type (e.g. `'cat'`, or `'cat' | 'kitten'` when several values\n * map to one subtype) that makes the union a true tagged union. Absent when\n * the subtype's tag can't be pinned to a literal (e.g. it appears in\n * multiple unions with different values).\n */\n discriminatorValue?: string;\n\n /** The raw OpenAPI `type` (used to distinguish integer from number). */\n openapiType?: string | string[];\n /** Vendor extensions (`x-*`) copied from the schema. */\n vendorExtensions?: VendorExtensions;\n /** True when the schema has additionalProperties. */\n hasAdditionalProperties?: boolean;\n /** The model describing additionalProperties values. */\n additionalPropertiesModel?: Model;\n /** True when the schema has patternProperties. */\n hasPatternProperties?: boolean;\n /** The models describing each pattern's values. */\n patternPropertiesModels?: PatternPropertyModel[];\n\n /** For composites: the referenced (object) models composed together. */\n composedModels?: Model[];\n /** For composites: the primitive/enum/array members composed together. */\n composedPrimitives?: Model[];\n /** For discriminated one-of/any-of composites: the discriminator metadata. */\n discriminator?: Discriminator;\n\n /** The TypeScript identifier for this model/property. */\n typescriptName?: string;\n /** The rendered TypeScript type. */\n typescriptType?: string;\n /** The Python identifier for this model/property. */\n pythonName?: string;\n /** The rendered Python type. */\n pythonType?: string;\n /** snake_case model name (Python). */\n nameSnakeCase?: string;\n /** True when the model is a renderable primitive (not composite/collection). */\n isPrimitive?: boolean;\n /** True when the schema declares an enum. */\n isEnum?: boolean;\n\n /** True for JSON-lines streaming responses. */\n isJsonlStreaming?: boolean;\n /** The model describing each streamed item. */\n itemSchemaModel?: Model;\n\n /** Collection serialisation format for array query/header parameters. */\n collectionFormat?: CollectionFormat;\n /** Path parameter serialization style, when not the default `simple`. */\n pathStyle?: 'matrix' | 'label';\n /** Path parameter explode flag (used with `pathStyle`). */\n pathExplode?: boolean;\n /** Query parameter `allowReserved`: reserved characters are not encoded. */\n allowReserved?: boolean;\n}\n\n/** Vendor extension bag (`x-*` keys copied from a schema/operation/spec). */\nexport type VendorExtensions = { [key: string]: unknown };\n\n/**\n * An operation represents a single OpenAPI path + method. Produced by the\n * parser and augmented by `../codegen-data.ts`.\n */\nexport interface Operation {\n id: string;\n name: string;\n method: string;\n path: string;\n description: string | null;\n tags?: string[] | null;\n deprecated: boolean;\n imports: string[];\n parameters: Model[];\n /** The request body parameter (same object reference as in `parameters`). */\n parametersBody?: Model | null;\n responses: Model[];\n\n /** Deduplicated operation name used throughout generated code. */\n uniqueName?: string;\n /** Dot-notation name (tag-qualified), used for metadata keys. */\n dotNotationName?: string;\n operationIdPascalCase?: string;\n operationIdSnakeCase?: string;\n /** The generated request type name (e.g. `FooRequest`). */\n requestTypeName?: string;\n\n /** The response model used as the operation's result. */\n result?: Model;\n\n /** The explicit body parameter when the body is not inlined. */\n explicitRequestBodyParameter?: Model;\n\n vendorExtensions?: VendorExtensions;\n isMutation?: boolean;\n isQuery?: boolean;\n isStreaming?: boolean;\n isInfiniteQuery?: boolean;\n /** The cursor parameter, for infinite queries. */\n infiniteQueryCursorProperty?: Model;\n}\n\n/**\n * A service groups operations. Currently the parser produces a single\n * `Default` service; operations are re-grouped by tag downstream.\n */\nexport interface Service {\n name: string;\n operations: Operation[];\n imports: string[];\n\n /** All model names the service (API client) needs to import. */\n modelImports?: string[];\n className?: string;\n nameSnakeCase?: string;\n}\n\n/**\n * The initial data structure produced by the OpenAPI parser.\n */\nexport interface ClientData {\n models: Model[];\n services: Service[];\n}\n\n/** Models indexed by name, for reference resolution during augmentation. */\nexport type ModelsByName = { [name: string]: Model };\n\n/** Index a list of models by name. */\nexport const indexModelsByName = (models: Model[]): ModelsByName =>\n Object.fromEntries(models.map((m) => [m.name, m]));\n\n/** The single service the parser emits; operations are re-grouped by tag. */\nexport const DEFAULT_SERVICE_NAME = 'Default';\n\n/**\n * The full data structure handed to the code generation templates.\n */\nexport interface CodeGenData extends ClientData {\n className: string;\n info: OpenAPIV3.InfoObject | OpenAPIV3_1.InfoObject;\n allOperations: Operation[];\n operationsByTag: { [tag: string]: Operation[] };\n untaggedOperations: Operation[];\n vendorExtensions: VendorExtensions;\n}\n\n/**\n * Create a {@link Model} with the default fields the parser always emits.\n * Callers override the fields relevant to their specific schema.\n */\nexport const createModel = (overrides: Partial<Model> = {}): Model => ({\n name: '',\n export: 'interface',\n type: 'unknown',\n description: null,\n deprecated: false,\n isNullable: false,\n isReadOnly: false,\n isRequired: false,\n link: null,\n properties: [],\n enum: [],\n imports: [],\n in: '',\n ...overrides,\n});\n\n// Model types which indicate it is composed (ie inherits/mixin's another schema)\nexport const COMPOSED_SCHEMA_TYPES = new Set<ModelExport>([\n 'one-of',\n 'any-of',\n 'all-of',\n]);\nexport const COLLECTION_TYPES = new Set<ModelExport>(['array', 'dictionary']);\nexport const PRIMITIVE_TYPES = new Set([\n 'string',\n 'integer',\n 'number',\n 'boolean',\n 'null',\n 'any',\n 'binary',\n 'void',\n]);\n\n/**\n * Content types that indicate JSON Lines streaming (OpenAPI 3.2)\n */\nexport const STREAMING_CONTENT_TYPES = new Set([\n 'application/jsonl',\n 'application/x-ndjson',\n]);\n\n/**\n * Vendor extensions which are used to customise generated code\n */\nexport const VENDOR_EXTENSIONS = {\n /**\n * Set to 'true' to indicate this is a streaming API\n */\n STREAMING: 'x-streaming',\n /**\n * Set to true to indicate this is a mutation, regardless of its HTTP method\n */\n MUTATION: 'x-mutation',\n /**\n * Set to true to indicate this is a query, regardless of its HTTP method\n */\n QUERY: 'x-query',\n /**\n * Set to the name of the input property used as the cursor for pagination if\n * the API accepts a cursor that is not named 'cursor'.\n * This can also be set to false to override behaviour and indicate this is not\n * a paginated API.\n * Used for tanstack infinite query hooks.\n */\n CURSOR: 'x-cursor',\n} as const;\n"],"names":["indexModelsByName","models","Object","fromEntries","map","m","name","DEFAULT_SERVICE_NAME","createModel","overrides","export","type","description","deprecated","isNullable","isReadOnly","isRequired","link","properties","enum","imports","in","COMPOSED_SCHEMA_TYPES","Set","COLLECTION_TYPES","PRIMITIVE_TYPES","STREAMING_CONTENT_TYPES","VENDOR_EXTENSIONS","STREAMING","MUTATION","QUERY","CURSOR"],"mappings":"AAAA;;;CAGC,GA6RD,oCAAoC,GACpC,OAAO,MAAMA,oBAAoB,CAACC,SAChCC,OAAOC,WAAW,CAACF,OAAOG,GAAG,CAAC,CAACC,IAAM;YAACA,EAAEC,IAAI;YAAED;SAAE,GAAG;AAErD,2EAA2E,GAC3E,OAAO,MAAME,uBAAuB,UAAU;AAc9C;;;CAGC,GACD,OAAO,MAAMC,cAAc,CAACC,YAA4B,CAAC,CAAC,GAAa,CAAA;QACrEH,MAAM;QACNI,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,MAAM;QACNC,YAAY,EAAE;QACdC,MAAM,EAAE;QACRC,SAAS,EAAE;QACXC,IAAI;QACJ,GAAGZ,SAAS;IACd,CAAA,EAAG;AAEH,iFAAiF;AACjF,OAAO,MAAMa,wBAAwB,IAAIC,IAAiB;IACxD;IACA;IACA;CACD,EAAE;AACH,OAAO,MAAMC,mBAAmB,IAAID,IAAiB;IAAC;IAAS;CAAa,EAAE;AAC9E,OAAO,MAAME,kBAAkB,IAAIF,IAAI;IACrC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAE;AAEH;;CAEC,GACD,OAAO,MAAMG,0BAA0B,IAAIH,IAAI;IAC7C;IACA;CACD,EAAE;AAEH;;CAEC,GACD,OAAO,MAAMI,oBAAoB;IAC/B;;GAEC,GACDC,WAAW;IACX;;GAEC,GACDC,UAAU;IACV;;GAEC,GACDC,OAAO;IACP;;;;;;GAMC,GACDC,QAAQ;AACV,EAAW"}
@@ -34,7 +34,7 @@ import { isRef, resolveIfRef, splitRef } from "./refs.js";
34
34
  }
35
35
  for (const model of data.models){
36
36
  assertNoClashingPropertyNames(model);
37
- assertNoConflictingUnionMemberMarshalling(model);
37
+ assertNoConflictingUnionMemberMarshalling(model, modelsByName);
38
38
  }
39
39
  for (const op of allOperations){
40
40
  assertEncodableUrlEncodedBody(op);
@@ -312,7 +312,7 @@ import { isRef, resolveIfRef, splitRef } from "./refs.js";
312
312
  /**
313
313
  * The marshalling semantics of a property — two properties with the same key
314
314
  * convert identically on the wire (so either branch's conversion is safe).
315
- */ const marshallingKey = (m)=>{
315
+ */ const marshallingKey = (m, modelsByName, visiting = new Set())=>{
316
316
  if ([
317
317
  'date',
318
318
  'date-time'
@@ -321,13 +321,38 @@ import { isRef, resolveIfRef, splitRef } from "./refs.js";
321
321
  // Enums serialise as their bare primitive (no conversion).
322
322
  if (m.isEnum || m.export === 'enum') return 'plain';
323
323
  if (COLLECTION_TYPES.has(m.export)) {
324
- return `${m.export}<${m.link ? marshallingKey(m.link) : 'plain'}>`;
324
+ return `${m.export}<${m.link ? marshallingKey(m.link, modelsByName, visiting) : 'plain'}>`;
325
325
  }
326
326
  if (m.export === 'tuple') {
327
- return `tuple<${m.properties.map(marshallingKey).join(',')}>`;
327
+ return `tuple<${m.properties.map((p)=>marshallingKey(p, modelsByName, visiting)).join(',')}>`;
328
328
  }
329
329
  if (m.export === 'reference' && !PRIMITIVE_TYPES.has(m.type)) {
330
- return `ref:${m.type}`;
330
+ // Key on what the reference marshals to, not what it is called. The
331
+ // normaliser hoists each inline schema to its own name, so two members
332
+ // declaring an identical property get different names — keying on the name
333
+ // alone rejected unions whose members convert identically.
334
+ const referenced = modelsByName[m.type];
335
+ if (!referenced || visiting.has(m.type)) return `ref:${m.type}`;
336
+ const nested = new Set(visiting).add(m.type);
337
+ if (referenced.export === 'one-of' || referenced.export === 'any-of') {
338
+ const members = [
339
+ ...referenced.composedModels ?? [],
340
+ ...referenced.composedPrimitives ?? []
341
+ ].map((member)=>marshallingKey(member, modelsByName, nested)).sort();
342
+ return `union<${[
343
+ ...new Set(members)
344
+ ].join('|')}>`;
345
+ }
346
+ if (referenced.export === 'interface') {
347
+ // Keyed on the shape too, for the same reason: two members declaring an
348
+ // identical inline object property are hoisted to different names.
349
+ const fields = (referenced.properties ?? []).filter((property)=>property.name).map((property)=>`${property.name}:${property.isRequired ? '!' : '?'}${marshallingKey(property, modelsByName, nested)}`).sort();
350
+ return `object<${fields.join(',')}>`;
351
+ }
352
+ // An `all-of` composes members that are resolved later, so it keeps its name
353
+ // as its key rather than a shape this pass cannot see yet.
354
+ if (referenced.export === 'all-of') return `ref:${m.type}`;
355
+ return marshallingKey(referenced, modelsByName, nested);
331
356
  }
332
357
  return 'plain';
333
358
  };
@@ -338,7 +363,7 @@ import { isRef, resolveIfRef, splitRef } from "./refs.js";
338
363
  * marshalling (e.g. a `date-time` in one and a plain string in another) —
339
364
  * otherwise the branch cannot be determined without guessing, so fail fast and
340
365
  * ask for a discriminator rather than corrupt data at runtime.
341
- */ const assertNoConflictingUnionMemberMarshalling = (model)=>{
366
+ */ const assertNoConflictingUnionMemberMarshalling = (model, modelsByName)=>{
342
367
  if (model.export !== 'one-of' && model.export !== 'any-of' || model.discriminator) {
343
368
  return;
344
369
  }
@@ -346,7 +371,7 @@ import { isRef, resolveIfRef, splitRef } from "./refs.js";
346
371
  for (const member of model.composedModels ?? []){
347
372
  for (const property of member.properties){
348
373
  if (!property.name) continue;
349
- const key = marshallingKey(property);
374
+ const key = marshallingKey(property, modelsByName);
350
375
  const existing = seen.get(property.name);
351
376
  if (existing && existing.key !== key) {
352
377
  throw new Error(`Schema "${model.name}" is a non-discriminated ${camelCase(model.export)} of "${existing.memberName}" and "${member.name}", which both declare a property "${property.name}" with different types, so the correct conversion cannot be determined. Add a discriminator to the union in your OpenAPI specification (e.g. with Pydantic, Field(discriminator=...)).`);