Skip to content

feat(js): Create tsplugingen - #10

Merged
aabedraba merged 1 commit into
prod-stagingfrom
aabedraba/js-plugin-generator
Sep 15, 2026
Merged

aabedraba merged 1 commit into
prod-stagingfrom
aabedraba/js-plugin-generator

Conversation

@aabedraba

@aabedraba aabedraba commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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_SPEC is 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 by sdk-range.sh, so the documented ../../../../js-sdk/...tgz example is looked up from the wrong directory here and the build cannot install the SDK. Normalize local specs to an absolute path before cd, while leaving registry specs unchanged.
	cd "$(BUILD)" && $(NPM) install --no-save --no-audit --no-fund "$(SDK_SPEC)"

js/tools/tsplugingen/Makefile:135

  • Any npm view failure is treated as an absent version here. A temporary registry, authentication, or network error therefore falls through to npm publish instead 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_spec is 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/http import requires @unikraft/cloud >=0.1.1, but SDK_RANGE is derived from any resolved SDK_SPEC without 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

  • CHANNEL is documented and commented as selecting the OpenAPI branch, but this default always resolves openapi-gen from prod-staging. A make 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 -0 lower bound is tied to the exact {major, minor, patch} tuple. Thus 0.1.1-next.0 produces >=0.1.1-0 <0.2.0, but npm's prerelease rules still do not admit 0.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 allOf without 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 using schemaToTsType before 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 generated params type 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.

Comment thread js/tools/tsplugingen/Makefile
Comment thread js/tools/tsplugingen/sdk-range.sh Outdated
@aabedraba
aabedraba force-pushed the aabedraba/js-plugin-generator branch 2 times, most recently from 9b11e4b to 3a75611 Compare September 14, 2026 21:02
@aabedraba
aabedraba requested a lite review from Copilot September 14, 2026 21:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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_GEN command, but that command uses @$(CHANNEL), which the Makefile describes as a generator branch. If that branch advances, the rendered sources can change while CONFIG_HASH stays the same and publish-check.sh skips 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.version is a YAML scalar and may be quoted. For a valid spec containing version: "1.2.3" (or version: "v1.2.3"), $2 retains 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 optional v.
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 properties and a oneOf/anyOf, the earlier composition branches are skipped and this fallback emits only schemaToTsType/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.body into query values; it only emits the parameters returned by queryParameters, 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

  • $oct is only set for application/octet-stream. A successful response with any other non-JSON content, such as text/plain or application/pdf, leaves $ret as void and calls request<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

Comment thread js/tools/tsplugingen/templates/resources.tmpl
Comment thread js/tools/tsplugingen/templates/index.ts.tmpl
Comment thread js/tools/tsplugingen/templates/models.ts.tmpl
Comment thread js/tools/tsplugingen/templates/resources.tmpl
Comment thread js/tools/tsplugingen/templates/resources.tmpl Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.tmpl deliberately generates AsyncGenerator methods for SSE and Uint8Array methods 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 Properties is also present. A schema with both oneOf and anyOf takes the oneOf branch and silently loses the anyOf, while an allOf-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_JS in go/tools/sdkgen/internal/sdk/name.go:103-127; here such a name produces an invalid package that npm publish rejects. 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, foo and Foo (or separator variants) can produce the same kebabcase filename and pascalcase class, causing one resource file to overwrite the other and duplicate members/imports in index.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 $oct select 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/json is treated as handled based only on its media type, even when its Media Type Object has no schema. In that valid case responseJSONSchema leaves $ret as void, so the generated request<void> discards the response body. Represent an untyped JSON body as unknown or 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, but headers is also read here as the transport's header map. A valid OpenAPI header or query parameter named headers therefore gets spread as a map and conflicts with the generated parameter, while a parameter named body similarly collides with the request-body field. Reserve all transport option names or keep operation parameters separate from CallOptions before 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 default as a successful response. In OpenAPI, default covers statuses not otherwise listed and is normally an error fallback; a valid operation with 200 JSON and default text/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, using default only 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

Comment thread js/tools/tsplugingen/Makefile Outdated
Comment thread js/tools/tsplugingen/templates/resources.tmpl Outdated
Comment thread js/tools/tsplugingen/templates/resources.tmpl

@nderjung nderjung left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-emptively approving after a review, the robot has some fair comments. Allows you to merge after those are fixed. Thanks!

Reviewed-by: Alexander Jung <alex@unikraft.com>
Approved-by: Alexander Jung <alex@unikraft.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-staging and 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/cloud tag. 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>

Comment on lines +38 to +42
readonly {{ camelcase $tag }}: {{ pascalcase $tag }}Api;
{{- end }}

constructor(config: ApiClientConfig) {
{{- range $tag := $tags }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) }}(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +160 to +164
{{- range $query }}
"{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }};
{{- end }}
{{- range $header }}
"{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +160 to +164
{{- range $query }}
"{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }};
{{- end }}
{{- range $header }}
"{{ .Name }}"{{ if not .Required }}?{{ end }}: {{ qualifyModels "models" (paramToTsType .) }};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }}(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +70 to +72
{{- 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 -}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@aabedraba
aabedraba force-pushed the aabedraba/js-plugin-generator branch from 1d2fbfc to da5fc10 Compare September 15, 2026 11:40
@aabedraba
aabedraba merged commit 02dcca3 into prod-staging Sep 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants