Skip to content

Add options for import file extensions and type-only imports - #1830

Open
Upgrade220 wants to merge 3 commits into
acacode:mainfrom
Upgrade220:feature/import-file-extension-and-type-only-imports
Open

Add options for import file extensions and type-only imports#1830
Upgrade220 wants to merge 3 commits into
acacode:mainfrom
Upgrade220:feature/import-file-extension-and-type-only-imports

Conversation

@Upgrade220

@Upgrade220 Upgrade220 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #1829

Problem

The generated client emits extensionless, value-only relative imports:

import { ApiResponse, Pet, User } from "./data-contracts";
import { ContentType, HttpClient, RequestParams } from "./http-client";

This breaks two increasingly common TypeScript setups, with no built-in option to change it — users must post-process the output by hand:

  1. Missing file extensions. moduleResolution: "node16" | "nodenext" requires explicit extensions on relative imports; the generated ./data-contracts / ./http-client fail to resolve at runtime (ESM) and are flagged by the compiler. allowImportingTsExtensions needs .ts instead.
  2. Value imports break verbatimModuleSyntax / isolatedModules. Type-only names (data contracts, RequestParams, HttpResponse, and ContentType in enumStyle: "union") are imported as values, which is an error under those flags.

The http-client import is mixed: HttpClient is a runtime class and must stay a value import, while RequestParams/HttpResponse are type-only — so it needs per-specifier inline type, not a whole-block import type.

Solution

Two new options, both defaulting to current behavior (no change to existing output):

  • importFileExtension: "" | ".js" | ".ts" (default "") — appended to generated relative imports. .js for node16/nodenext, .ts for allowImportingTsExtensions.
  • typeOnlyImports: boolean (default false) — emits import type for the wholly type-only data-contracts import, and inline type on the mixed http-client import, keeping HttpClient a value import. ContentType is marked type only for enumStyle: "union" (where it is a pure type); for enum/const/const-enum it stays a runtime value.

Both are exposed as CLI flags (--import-file-extension, --type-only-imports).

Implemented at the template level (templates/modular/api.ejs, templates/modular/route-types.ejs, templates/default/route-types.ejs) rather than as a post-processing pass, since the templates already know the import paths and which specifiers are types.

Example — replacing a typical hand-rolled Node16 post-process:

generateApi({
  importFileExtension: ".js",
  typeOnlyImports: true,
});

Produces:

import type { ApiResponse, Pet, User } from "./data-contracts.js";
import { ContentType, HttpClient, type RequestParams } from "./http-client.js";

Verification

  • New spec suite tests/spec/import-file-extension/ (8 tests): .js / .ts extensions, default (no extension), whole-block import type for data-contracts, inline type on the mixed http-client import with HttpClient kept as a value, ContentType value-vs-type per enum style, and the combined case.
  • All existing snapshot tests pass unchanged, confirming default output is byte-identical.
  • bun run build passes; no new lint warnings/errors introduced.

Notes

  • Adds a changeset (minor).
  • One unrelated commit adds /.idea/ and /.serena/ to .gitignore.

Summary by cubic

Adds importFileExtension and typeOnlyImports options so generated relative imports work with moduleResolution: node16/nodenext, allowImportingTsExtensions, and verbatimModuleSyntax/isolatedModules. Both default to current behavior, so existing output is byte-identical.

New Features

  • importFileExtension ("" | ".js" | ".ts") appends an extension to generated relative imports; use .js for node16/nodenext and .ts for allowImportingTsExtensions.
  • Invalid importFileExtension values are rejected at runtime; explicit undefined normalizes to "".
  • typeOnlyImports emits whole-block import type for data-contracts and inline type on the mixed http-client import, keeping HttpClient as a value import.
  • ContentType is only marked type for enumStyle: "union", where it is a pure type.
  • Both options are exposed as CLI flags: --import-file-extension and --type-only-imports.
  • Adds a spec suite covering extension, type-only, combined, and route-type imports.
  • Also adds /.idea/ and /.serena/ to .gitignore.

Written for commit 4f0dcb2. Summary will update on new commits.

Review in cubic

Ковальков Кирилл Андреевич and others added 2 commits September 3, 2026 16:57
Generated relative imports were extensionless and value-only, which
breaks moduleResolution node16/nodenext (needs .js), allowImportingTsExtensions
(needs .ts), and verbatimModuleSyntax (needs import type). Users had to
post-process the output by hand.

- importFileExtension ("" | ".js" | ".ts") appends an extension to
  generated relative imports.
- typeOnlyImports emits `import type` for the wholly type-only
  data-contracts import and inline `type` for the mixed http-client
  import, keeping HttpClient a value import. ContentType is marked
  `type` only for enumStyle "union".

Both are exposed as CLI flags. Defaults preserve current output.

Closes acacode#1829

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4f0dcb2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
swagger-typescript-api Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="index.ts">

<violation number="1" location="index.ts:364">
P2: When a CLI user passes a value other than `""`, `.js`, or `.ts`, this flag accepts it and the cast does not validate it at runtime. Validate and reject unsupported extensions before passing the value to `generateApi`, otherwise generation can produce imports that do not point to generated files.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread index.ts
| "const"
| "const-enum"
| undefined,
importFileExtension: args["import-file-extension"] as

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When a CLI user passes a value other than "", .js, or .ts, this flag accepts it and the cast does not validate it at runtime. Validate and reject unsupported extensions before passing the value to generateApi, otherwise generation can produce imports that do not point to generated files.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At index.ts, line 364:

<comment>When a CLI user passes a value other than `""`, `.js`, or `.ts`, this flag accepts it and the cast does not validate it at runtime. Validate and reject unsupported extensions before passing the value to `generateApi`, otherwise generation can produce imports that do not point to generated files.</comment>

<file context>
@@ -349,6 +361,12 @@ const generateCommand = defineCommand({
         | "const"
         | "const-enum"
         | undefined,
+      importFileExtension: args["import-file-extension"] as
+        | ""
+        | ".js"
</file context>

Comment thread tests/spec/import-file-extension/basic.test.ts
Address PR review feedback:
- Reject unsupported importFileExtension values at runtime (in config
  update), so CLI/config-file callers that bypass the type get a clear
  error instead of broken imports. Explicit undefined normalizes to "".
- Add route-type coverage (generateRouteTypes) asserting the extension
  and type-only import behavior of route-types.ejs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Upgrade220

Copy link
Copy Markdown
Contributor Author

Thanks for the review — both addressed in 4f0dcb2:

  1. CLI validation (index.ts:364): importFileExtension is now validated at runtime in CodeGenConfig.update, so any entry point (CLI, config file, JS library) that bypasses the type gets a clear error (Invalid importFileExtension value ".mjs". Expected "", ".js", or ".ts".) instead of generating imports that don't resolve. Explicit undefined normalizes to "".
  2. Route-type test coverage: added tests that generate with generateRouteTypes: true and assert the route-types file (ApiRoute.ts) gets both the file extension and the import type treatment, so route-types.ejs is no longer only exercised implicitly.

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.

Add options for import file extensions (.js/.ts) and type-only imports

1 participant