feat(js): Create tsplugingen - #10
Conversation
d9cdc63 to
f4f57e5
Compare
c0a29a5 to
8c7dd28
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate findings affect generation correctness, SDK compatibility, and publishing reliability.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a Makefile-driven generator for publishable TypeScript clients backed by @unikraft/cloud.
Changes:
- Adds templates for APIs, models, package metadata, and documentation.
- Adds generation, build, peer-range, fingerprint, and publish workflows.
- Adds TypeScript/Biome configuration, documentation, and ignore rules.
File summaries
| File | Review summary |
|---|---|
js/tools/tsplugingen/templates/resources.tmpl |
Moderate finding (1 vote): merge operation parameters over path-item parameters to avoid duplicate generated properties. |
js/tools/tsplugingen/templates/README.md.tmpl |
No final findings recorded. |
js/tools/tsplugingen/templates/package.json.tmpl |
No final findings recorded. |
js/tools/tsplugingen/templates/models.ts.tmpl |
Moderate finding (1 vote): render primitive, array, and property-less schemas using schemaToTsType rather than empty interfaces. |
js/tools/tsplugingen/templates/index.ts.tmpl |
No final findings recorded. |
js/tools/tsplugingen/static/tsconfig.json |
No final findings recorded. |
js/tools/tsplugingen/static/biome.json |
No final findings recorded. |
js/tools/tsplugingen/sdk-range.sh |
Moderate findings: correct 0.0.z upper bounds (2 votes) and account for future prereleases (1 vote). |
js/tools/tsplugingen/README.md |
No final findings recorded. |
js/tools/tsplugingen/Makefile |
Moderate findings: resolve relative SDK specs before changing directories (1 vote); include all generation inputs in fingerprints (3 votes); distinguish registry lookup failures (1 vote); fail safely when fingerprint metadata lookup fails (1 vote); enforce the SDK transport minimum (1 vote); and honor CHANNEL when selecting the generator (1 vote). |
js/tools/tsplugingen/.gitignore |
No final findings recorded. |
Review details
Suppressed comments (9)
js/tools/tsplugingen/Makefile:120
SDK_SPECis documented as accepting a relative tarball/path, but this recipe changes into.build/<plugin>before passing it to npm. The same relative value was resolved from the tool directory bysdk-range.sh, so the documented../../../../js-sdk/...tgzexample is looked up from the wrong directory here and the build cannot install the SDK. Normalize local specs to an absolute path beforecd, while leaving registry specs unchanged.
cd "$(BUILD)" && $(NPM) install --no-save --no-audit --no-fund "$(SDK_SPEC)"
js/tools/tsplugingen/Makefile:135
- Any
npm viewfailure is treated as an absent version here. A temporary registry, authentication, or network error therefore falls through tonpm publishinstead of failing the lookup, making the publish step unreliable. Distinguish a successful 404 from other lookup errors and retry or fail the latter.
if ! $(NPM) view "$$name@$$ver" version >/dev/null 2>&1; then \
echo "publishing $$name@$$ver (tag: $(DIST_TAG))"; \
$(NPM) publish --access public --tag "$(DIST_TAG)" $(PUBLISH_FLAGS); \
exit $$?; \
fi; \
js/tools/tsplugingen/Makefile:140
- When the hash metadata lookup fails,
was_specis empty and this branch reports that the package predates fingerprints, then exits successfully. A registry outage can therefore silently bypass the source-integrity check. Only take this path after a successful metadata lookup confirms the field is absent; otherwise fail or retry.
was_spec=$$($(NPM) view "$$name@$$ver" unikraft.specHash 2>/dev/null); \
was_tmpl=$$($(NPM) view "$$name@$$ver" unikraft.templatesHash 2>/dev/null); \
if [ -z "$$was_spec" ]; then \
echo "warning: $$name@$$ver predates source fingerprints, so it cannot be checked"; \
exit 0; \
js/tools/tsplugingen/Makefile:151
- The final message is unconditional, so it says “A new version must ship them” even when only the specification hash changed and the templates are identical. This makes the remediation misleading; print that sentence only inside the template-mismatch branch.
[ "$$was_tmpl" = "$(TEMPLATES_HASH)" ] || \
echo " The generator templates or the static build config changed."; \
echo " A new version must ship them."; \
js/tools/tsplugingen/Makefile:29
- The Makefile says the default
@unikraft/cloud/core/httpimport requires@unikraft/cloud >=0.1.1, butSDK_RANGEis derived from any resolvedSDK_SPECwithout enforcing that floor. With an older SDK spec such as@unikraft/cloud@0.1.0, generation writes a peer range that permits the unsupported version and the subsequent build cannot resolve this subpath. Reject SDK versions below the transport's minimum or derive the import/range together.
# A leaf subpath, so the plugin package does not pull the SDK's idiomatic layer
# into its module graph. Needs @unikraft/cloud >=0.1.1, which exports it.
CLIENT_IMPORT ?= @unikraft/cloud/core/http
js/tools/tsplugingen/Makefile:20
CHANNELis documented and commented as selecting the OpenAPI branch, but this default always resolvesopenapi-genfromprod-staging. Amake CHANNEL=prod-stable ...build therefore still uses staging generator code while publishing against the stable SDK/tag. Use the channel in this default so the channel-dependent inputs stay aligned.
OPENAPI_GEN ?= $(GO) run unikraft.com/x/tools/openapi-gen@prod-staging
js/tools/tsplugingen/sdk-range.sh:47
- The
-0lower bound is tied to the exact{major, minor, patch}tuple. Thus0.1.1-next.0produces>=0.1.1-0 <0.2.0, but npm's prerelease rules still do not admit0.1.2-next.0; once the staging SDK advances, this plugin's unchanged package can install a second@unikraft/cloud. The publish/range policy needs to account for future prereleases rather than only the current tuple.
const lower = v.includes("-") ? `${major}.${minor}.${patch}-0` : `${major}.${minor}.${patch}`;
// Pre-1.0 packages break on the minor, so that is where the range stops.
const upper = major === "0" ? `0.${+minor + 1}.0` : `${+major + 1}.0.0`;
console.log(`>=${lower} <${upper}`);
js/tools/tsplugingen/templates/models.ts.tmpl:28
- Named schemas that are primitives or arrays (and schemas such as
allOfwithout direct properties) fall through to this branch, so the generated type becomes an empty interface instead of the schema's wire type. That makes generated request/response types incorrect; add a non-object branch usingschemaToTsTypebefore emitting the interface, as the Go model template does.
{{- else }}
export interface {{ $name }} {
js/tools/tsplugingen/templates/resources.tmpl:83
- OpenAPI operation parameters override path-item parameters with the same
(name, in)pair, but this appends both header definitions. A valid override can therefore produce duplicate/conflicting properties in the generatedparamstype and emit the header twice; merge these lists with the operation-level definition winning before rendering.
{{- range $op.Parameters -}}{{- with .Value -}}
{{- if eq .In "header" -}}{{- $header = append $header . -}}{{- end -}}
{{- end -}}{{- end -}}
- Files reviewed: 11/11 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
9b11e4b to
3a75611
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate correctness issues affect generated clients and publication integrity.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (5)
js/tools/tsplugingen/Makefile:72
- The fingerprint includes the literal
OPENAPI_GENcommand, but that command uses@$(CHANNEL), which the Makefile describes as a generator branch. If that branch advances, the rendered sources can change whileCONFIG_HASHstays the same andpublish-check.shskips the already-published version. Pin or resolve the generator revision (or hash its resolved source) before computing this fingerprint.
CONFIG_HASH = $(shell printf '%s\n%s\n%s\n' "$(SDK_RANGE)" "$(CLIENT_IMPORT)" "$(OPENAPI_GEN)" | $(SHA256) | cut -c1-64)
js/tools/tsplugingen/Makefile:57
info.versionis a YAML scalar and may be quoted. For a valid spec containingversion: "1.2.3"(orversion: "v1.2.3"),$2retains the quotes, producing an invalid npm package version and causing generation/build to fail. Use a YAML-aware parser or remove YAML quoting before stripping the optionalv.
VERSION = $(shell awk '/^info:/{f=1;next} /^[^[:space:]#]/{f=0} f&&/^[[:space:]]+version:/{v=$$2; sub(/^v/,"",v); print v; exit}' $(SPEC))
js/tools/tsplugingen/templates/models.ts.tmpl:32
- When an object schema has both
propertiesand aoneOf/anyOf, the earlier composition branches are skipped and this fallback emits onlyschemaToTsType/an interface, dropping the composition alternatives. That produces request/response types that no longer represent the OpenAPI schema; preserve the composition (for example, intersect the object members with the union) or reject unsupported composed schemas instead of silently erasing it.
{{- else if and (ne $openAPIType "object") (not $schema.Properties) }}
{{- /* A named schema of a non-object shape - an array of strings, say - is the
type it encodes to under a name of its own, not an empty interface. */}}
export type {{ $name }} = {{ schemaToTsType $schema }};
{{- else }}
js/tools/tsplugingen/templates/resources.tmpl:101
- These assignments discard the request body for every GET/HEAD operation. The template does not transform
params.bodyinto query values; it only emits the parameters returned byqueryParameters, so a body-only field (or a required body) becomes impossible to send. Reject such specs or explicitly map the body schema to query parameters instead of silently clearing it.
{{- if or (eq .Method "GET") (eq .Method "HEAD") -}}
{{- $bodyRef = false -}}
{{- $bodyReq = false -}}
js/tools/tsplugingen/templates/resources.tmpl:68
$octis only set forapplication/octet-stream. A successful response with any other non-JSON content, such astext/plainorapplication/pdf, leaves$retasvoidand callsrequest<void>, so the generated method cannot return that response body. Detect all supported non-JSON media types and use a raw-body path, or fail generation for unsupported types.
{{- $oct := false -}}
{{- range $entry := sortedResponseCodes $op.Responses -}}
{{- if and (not $oct) (or (hasPrefix "2" $entry.Code) (eq $entry.Code "default")) -}}
{{- with $entry.Ref -}}{{- with .Value -}}
{{- with index .Content "application/octet-stream" -}}{{- $oct = true -}}{{- end -}}
{{- end -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- if and $oct (eq $ret "void") -}}
{{- $ret = "Uint8Array" -}}
- Files reviewed: 12/12 changed files
- Comments generated: 5
- Review effort level: Lite
3a75611 to
eaa8f54
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved generation correctness issues and an unquoted Makefile command create approval-blocking risks.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (9)
js/tools/tsplugingen/templates/README.md.tmpl:15
- This says every method returns a response envelope, but
resources.tmpldeliberately generatesAsyncGeneratormethods for SSE andUint8Arraymethods for octet-stream responses. Consumers following this README will use the wrong return type for those operations; document the JSON, streaming, and binary cases separately.
This package is the raw ("plumbing") layer. Methods are named after their
`operationId` and return the response envelope exactly as the specification
describes it. If you want the idiomatic layer, use
[`@unikraft/cloud`](https://www.npmjs.com/package/@unikraft/cloud), which wraps
this package and creates the instance for you.
js/tools/tsplugingen/templates/models.ts.tmpl:15
- This guard only rejects compositions when
Propertiesis also present. A schema with bothoneOfandanyOftakes theoneOfbranch and silently loses theanyOf, while anallOf-only object falls through to the object branch and can become an empty interface. Reject every composition this template does not render, or emit an intersection/union for it instead of generating an incomplete wire type.
{{- if and $schema.Properties (or $schema.OneOf $schema.AnyOf $schema.AllOf) }}
{{- fail (printf "schema %s combines properties with oneOf, anyOf or allOf, which the templates cannot render" $name) }}
{{- end }}
js/tools/tsplugingen/templates/package.json.tmpl:9
- The plugin name is inserted verbatim into the npm package name, although plugin names are not guaranteed to be lowercase, URL-safe slugs. The sibling generator normalizes names such as
Example_JSingo/tools/sdkgen/internal/sdk/name.go:103-127; here such a name produces an invalid package thatnpm publishrejects. Derive a separate npm-safe name and use it consistently in the manifest, README/import examples, and Makefile, or reject it before generation.
"name": "@unikraft/cloud-plugin-{{ $plugin }}-api",
js/tools/tsplugingen/templates/resources.tmpl:13
- This validation checks each tag's normalized class name independently, but not collisions between distinct tags. For example,
fooandFoo(or separator variants) can produce the samekebabcasefilename andpascalcaseclass, causing one resource file to overwrite the other and duplicate members/imports inindex.ts. Reject duplicate normalized tag names before rendering, or disambiguate every derived name consistently.
{{- range $tag := uniqueTags $ops }}{{ if not (regexMatch "^[A-Za-z_$][A-Za-z0-9_$]*$" (pascalcase $tag)) }}
{{- fail (printf "tag %q cannot name a TypeScript class; rename it in the plugin's api.tsp" $tag) }}
{{- end }}{{ end -}}
js/tools/tsplugingen/templates/resources.tmpl:90
- The validation allows heterogeneous successful responses, but each generated method has one fixed transport call and return type. If one 2xx response is JSON while another is SSE or octet-stream,
$ret,$sse, and$octselect one path and the other status is decoded with the wrong method. Reject mixed success response shapes/media types or generate explicit dispatch/union handling.
{{- range $entry := sortedResponseCodes $op.Responses -}}
{{- if or (hasPrefix "2" $entry.Code) (eq $entry.Code "default") -}}
{{- with $entry.Ref -}}{{- with .Value -}}{{- if .Content -}}
{{- $handled := false -}}
{{- range $ct, $_ := .Content -}}
js/tools/tsplugingen/templates/resources.tmpl:94
application/jsonis treated as handled based only on its media type, even when its Media Type Object has no schema. In that valid caseresponseJSONSchemaleaves$retasvoid, so the generatedrequest<void>discards the response body. Represent an untyped JSON body asunknownor fail generation.
{{- range $ct, $_ := .Content -}}
{{- if has $ct (list "application/json" "text/event-stream" "application/octet-stream") -}}{{- $handled = true -}}{{- end -}}
{{- end -}}
{{- if not $handled -}}
{{- fail (printf "%s %s: the %s response has no media type that the templates can return" $po.Method $po.Path $entry.Code) -}}
js/tools/tsplugingen/templates/resources.tmpl:188
- Converting every header parameter with
String()breaks valid object-valued header parameters: an object becomes[object Object]instead of the OpenAPI header serialization (for example, simple key/value pairs). Serialize according to the parameter's style/explode rules, or reject non-scalar header schemas during generation.
...(params["{{ .Name }}"] !== undefined ? { "{{ .Name }}": String(params["{{ .Name }}"]) } : {}),
js/tools/tsplugingen/templates/resources.tmpl:190
- Operation parameters share the same object as
CallOptions, butheadersis also read here as the transport's header map. A valid OpenAPI header or query parameter namedheaderstherefore gets spread as a map and conflicts with the generated parameter, while a parameter namedbodysimilarly collides with the request-body field. Reserve all transport option names or keep operation parameters separate fromCallOptionsbefore merging them.
headers: {
{{- range $header }}
...(params["{{ .Name }}"] !== undefined ? { "{{ .Name }}": String(params["{{ .Name }}"]) } : {}),
{{- end }}
...params.headers,
js/tools/tsplugingen/templates/resources.tmpl:91
- These response scans treat
defaultas a successful response. In OpenAPI,defaultcovers statuses not otherwise listed and is normally an error fallback; a valid operation with200JSON anddefaulttext/plain will fail generation here, and a default stream/bytes response can alter the generated method. Restrict the SSE/bytes/media validation scans to 2xx responses, usingdefaultonly as a fallback when no 2xx response exists.
{{- if or (hasPrefix "2" $entry.Code) (eq $entry.Code "default") -}}
{{- with $entry.Ref -}}{{- with .Value -}}{{- if .Content -}}
{{- $handled := false -}}
{{- range $ct, $_ := .Content -}}
{{- if has $ct (list "application/json" "text/event-stream" "application/octet-stream") -}}{{- $handled = true -}}{{- end -}}
- Files reviewed: 12/12 changed files
- Comments generated: 3
- Review effort level: Lite
eaa8f54 to
1d2fbfc
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect generated-code correctness, compatibility, and reproducibility.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (7)
js/tools/tsplugingen/templates/README.md.tmpl:21
- The default build targets
prod-stagingand writes a prerelease peer range (for example>=0.1.1-0 <0.2.0), but this generated install command asks npm for the stable@unikraft/cloudtag. Until that SDK version is stable, the command resolves an older incompatible peer and installation fails; the README needs to identify the channel-specific SDK spec or provide the correct peer-install instructions.
```sh
npm install @unikraft/cloud-plugin-{{ $plugin }}-api @unikraft/cloud
**js/tools/tsplugingen/templates/package.json.tmpl:48**
* The build installs these caret-ranged compiler and formatter versions without a committed lockfile, so the same spec/template hashes can resolve different tool versions on different runs. That can change emitted declarations or formatting while `publish-check.sh` still treats the source fingerprints as identical and skips the version. Pin the build tools (or include their resolved versions in the generation fingerprint) to keep published output reproducible.
"devDependencies": {
"@biomejs/biome": "^2.5.13",
"typescript": "^7.0.2"
**js/tools/tsplugingen/templates/resources.tmpl:95**
* This check only fails when no JSON request schema exists. An operation that advertises both `application/json` and another request media type still generates a JSON-only body and silently makes the other valid representation impossible to send. Reject mixed request content or generate an explicit content-type/body union instead of proceeding when any non-JSON media type is present.
{{- if and $op.RequestBody (not $bodyRef) -}}
{{- fail (printf "%s %s: the request body is not application/json, and the templates send JSON only" $po.Method $po.Path) -}}
**js/tools/tsplugingen/templates/resources.tmpl:197**
* `CallOptions.headers` can be a standard fetch `HeadersInit` such as a `Headers` instance or a tuple array, not only a plain record. Object-spreading those forms does not copy their entries (`Headers` has no enumerable pairs and arrays produce numeric keys), so caller-supplied headers are lost whenever this operation also has generated headers. Normalize `params.headers` with the transport's header merge helper (or an equivalent `Headers` conversion) before adding these parameters.
...params.headers,
},
**js/tools/tsplugingen/templates/resources.tmpl:156**
* A valid path parameter named `params` will be emitted as `params: ...` here, followed by the options argument also named `params`, producing duplicate parameter names and an uncompilable method. Either generate the call-options argument under a collision-free name (and use it consistently) or fail generation when a path parameter normalizes to that name.
{{ end }}{{ end }} {{ tsSafeName (camelcase $op.OperationID) }}(
{{- range pathParameters $po }}
{{ tsSafeName .Name }}: {{ qualifyModels "models" (paramToTsType .) }},
**js/tools/tsplugingen/templates/resources.tmpl:183**
* Only the parameter name/value is passed to the transport here; `style`, `explode`, and `allowReserved` are discarded. A valid `spaceDelimited`, `pipeDelimited`, `deepObject`, or reserved-character query parameter will consequently be serialized using the transport default rather than the OpenAPI contract. Emit the appropriate query entries or reject unsupported styles during generation.
query: {
{{- range $query }}
"{{ .Name }}": params["{{ .Name }}"],
{{- end }}
},
**js/tools/tsplugingen/templates/resources.tmpl:83**
* This check fails only when a response has no supported media type. A response declaring both `application/json` and an unsupported representation such as `text/plain` is accepted, but the generated request sends no `Accept` constraint and the transport can receive the unsupported representation that the method cannot decode. Reject any unsupported alternative or emit an `Accept` header selecting the handled type.
{{- if not $handled -}}
{{- fail (printf "%s %s: the %s response has no media type that the templates can return" $po.Method $po.Path $entry.Code) -}}
{{- end -}}
- **Files reviewed:** 12/12 changed files
- **Comments generated:** 8
- **Review effort level:** Lite
</details>
| readonly {{ camelcase $tag }}: {{ pascalcase $tag }}Api; | ||
| {{- end }} | ||
|
|
||
| constructor(config: ApiClientConfig) { | ||
| {{- range $tag := $tags }} |
There was a problem hiding this comment.
Fixed. The tag preflight in resources.tmpl now rejects a tag whose camelcase form is constructor, next to the existing class-name check.
Checked the claim first: readonly constructor: XApi is a parse error, TS1005. Other reserved words are legal property names, so readonly class, readonly delete and readonly type all compile. Only constructor is rejected.
| {{- with $p.Description }} | ||
| {{ tsDoc . " " | trim }} | ||
| {{- end }} | ||
| "{{ $prop }}"{{ if not (getPropertyRequired $schema $prop) }}?{{ end }}: {{ schemaToTsType $p }}; |
There was a problem hiding this comment.
Fixed. The key goes through toJson, which quotes and escapes it.
Reproduced it first with a property named odd"key. The old template emitted "odd"key"?: string;, which is invalid. It now emits "odd\"key"?: string;. A name that needs no escaping renders byte-for-byte as before, so no plugin churns.
| {{- range $header }}{{- if .Required }}{{- $paramsReq = true -}}{{- end -}}{{- end }} | ||
| {{ with $op.Summary }}{{ tsDoc . " " }} | ||
| {{ else }}{{ with $op.Description }}{{ tsDoc . " " }} | ||
| {{ end }}{{ end }} {{ tsSafeName (camelcase $op.OperationID) }}( |
There was a problem hiding this comment.
Fixed. The tag preflight now renders every operationId under the tag and fails on a duplicate, and the message names the operationId and the method.
The scope is the tag, because the tag is the class the methods land in. The same method name under two tags gives two classes, which stays legal.
Verified: a spec with get-user and getUser under one tag fails generation.
| {{- range $query }} | ||
| "{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }}; | ||
| {{- end }} | ||
| {{- range $header }} | ||
| "{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }}; |
There was a problem hiding this comment.
Fixed. Generation fails when one name is both a query and a header parameter.
Confirmed the generated type was invalid: a duplicate key gives TS2300, plus TS2717 when the two types differ.
| {{- range $query }} | ||
| "{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }}; | ||
| {{- end }} | ||
| {{- range $header }} | ||
| "{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }}; |
There was a problem hiding this comment.
Fixed by the same toJson change, at the declaration and at both accesses: the query value and the header spread.
A header named X-Weird\Head now emits "X-Weird\\Head" in all three places.
| params: CallOptions = {}, | ||
| {{- end }} | ||
| ): {{ if $sse }}AsyncGenerator<{{ $ret }}, void, void>{{ else }}Promise<{{ $ret }}>{{ end }} { | ||
| return this.{{ if $sse }}stream<{{ $ret }}>{{ else if $oct }}bytes{{ else }}request<{{ $ret }}>{{ end }}( |
There was a problem hiding this comment.
Fixed. The tag preflight rejects an operationId that renders request, stream or bytes.
Rejection rather than super, for two reasons. The override is also a type error, TS2416, because the base signature is generic. A consumer who calls api.request(...) would get the operation instead of the transport.
constructor needs no entry, because tsSafeName already renames it to _constructor.
| {{- $path := .Path -}} | ||
| {{- /* ApiClient does not escape `path`, so a "/" or "?" in a path parameter | ||
| would change the route. */ -}} | ||
| {{- range pathParameters $po }}{{ $path = replace (printf "{%s}" .Name) (printf "${encodeURIComponent(%s)}" (tsSafeName .Name)) $path }}{{ end -}} |
There was a problem hiding this comment.
Fixed by rejection, which mirrors the header-parameter check above it: generation fails when a path parameter's schema is an object or an array.
Full style and explode serialization is not worth the templates until a plugin spec asks for it.
| {{- with index .Content "application/json" -}} | ||
| {{- $handled = true -}}{{- $kinds = append $kinds "application/json" -}} | ||
| {{- if eq $ret "void" -}}{{- $ret = "unknown" -}}{{- with .Schema -}}{{- $ret = qualifyModels "models" (tsTypeRef .) -}}{{- end -}}{{- end -}} |
There was a problem hiding this comment.
Fixed. The template collects every success schema and returns their union, deduplicated.
200 Existing with 201 Created now gives Promise<models.Existing | models.Created>. Two responses that share a schema still give the single type, so existing plugins do not churn.
Signed-off-by: aabedraba <abdallah@unikraft.com>
1d2fbfc to
da5fc10
Compare
Blocked by unikraft-cloud/js-sdk#27
Blocks https://github.com/unikraft-cloud/plugins/pull/27
Signed-off-by: aabedraba abdallah@unikraft.com