fork of hey-api/openapi-ts because I need some additional things

Merge pull request #2562 from hey-api/feat/codegen-core

feat(codegen-core): initial commit

authored by

Lubos and committed by
GitHub
fe2803aa c15a8e14

+1796 -347
+5
.changeset/silly-apricots-exist.md
··· 1 + --- 2 + '@hey-api/codegen-core': patch 3 + --- 4 + 5 + feat: initial release
docs/public/bricks.png

This is a binary file and will not be displayed.

docs/public/images/bricks-300w.png

This is a binary file and will not be displayed.

docs/public/images/bricks-640w.png

This is a binary file and will not be displayed.

+13
docs/scripts/optimize-images.js
··· 22 22 sizes: [ 23 23 { 24 24 formats: ['png'], 25 + width: 300, 26 + }, 27 + { 28 + formats: ['png'], 29 + width: 640, 30 + }, 31 + ], 32 + source: 'bricks.png', 33 + }, 34 + { 35 + sizes: [ 36 + { 37 + formats: ['png'], 25 38 width: 16, 26 39 }, 27 40 {
+16
packages/codegen-core/.gitignore
··· 1 + .DS_Store 2 + .idea 3 + .tsup 4 + .tmp 5 + junit.xml 6 + logs 7 + node_modules 8 + npm-debug.log* 9 + temp 10 + yarn-debug.log* 11 + yarn-error.log* 12 + 13 + *.iml 14 + dist 15 + coverage 16 + .env
+21
packages/codegen-core/LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) Hey API 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+126
packages/codegen-core/README.md
··· 1 + <div align="center"> 2 + <img alt="Three people building a brick structure" height="214" src="https://heyapi.dev/images/bricks-640w.png" width="320"> 3 + <h1 align="center"><b>Codegen Core</b></h1> 4 + <p align="center">🧱 TypeScript framework for generating structured, multi-file source code from abstract syntax trees.</p> 5 + </div> 6 + 7 + <br/> 8 + 9 + <p align="center"> 10 + <a href="https://opensource.org/license/mit" rel="nofollow"><img src="https://img.shields.io/github/license/hey-api/openapi-ts" alt="MIT License"></a> 11 + <a href="https://github.com/hey-api/openapi-ts/actions?query=branch%3Amain"><img src="https://github.com/hey-api/openapi-ts/actions/workflows/ci.yml/badge.svg?event=push&branch=main" alt="CI status" /></a> 12 + </p> 13 + 14 + <p align="center"> 15 + <a href="https://github.com/hey-api/openapi-ts/issues">Issues</a> 16 + <span>&nbsp;•&nbsp;</span> 17 + <a href="https://heyapi.dev/openapi-ts/community/contributing">Contribute</a> 18 + <span>&nbsp;•&nbsp;</span> 19 + <a href="https://heyapi.dev/openapi-ts/community/spotlight#core-team">Join Core Team</a> 20 + </p> 21 + 22 + <br/> 23 + 24 + ## Dashboard 25 + 26 + Hey API is an ecosystem of products helping you build better APIs. Superpower your codegen and APIs with Hey API Platform. 27 + 28 + [Sign In](https://app.heyapi.dev) to Hey API Platform. 29 + 30 + ## Contributing 31 + 32 + Want to see your code in products used by millions? 33 + 34 + Start with our [Contributing](https://heyapi.dev/openapi-ts/community/contributing) guide and release your first feature. 35 + 36 + ## Sponsors 37 + 38 + Help Hey API stay around for the long haul by becoming a [sponsor](https://github.com/sponsors/hey-api). 39 + 40 + <h3 align="center">Gold</h3> 41 + 42 + <table align="center" style="justify-content: center;align-items: center;display: flex;"> 43 + <tbody> 44 + <tr> 45 + <td align="center"> 46 + <p></p> 47 + <p> 48 + <a href="https://kutt.it/pkEZyc" target="_blank"> 49 + <picture height="50px"> 50 + <source media="(prefers-color-scheme: dark)" srcset="https://heyapi.dev/images/stainless-logo-wordmark-480w.jpeg"> 51 + <img alt="Stainless logo" height="50px" src="https://heyapi.dev/images/stainless-logo-wordmark-480w.jpeg"> 52 + </picture> 53 + </a> 54 + <br/> 55 + Best-in-class SDKs and MCP for your API. 56 + <br/> 57 + <a href="https://kutt.it/pkEZyc" style="text-decoration:none;" target="_blank"> 58 + stainless.com 59 + </a> 60 + </p> 61 + <p></p> 62 + </td> 63 + </tr> 64 + </tbody> 65 + </table> 66 + 67 + <h3 align="center">Silver</h3> 68 + 69 + <table align="center" style="justify-content: center;align-items: center;display: flex;"> 70 + <tbody> 71 + <tr> 72 + <td align="center"> 73 + <a href="https://kutt.it/skQUVd" target="_blank"> 74 + <picture height="40px"> 75 + <source media="(prefers-color-scheme: dark)" srcset="https://heyapi.dev/images/scalar-logo-wordmark-480w.jpeg"> 76 + <img alt="Scalar logo" height="40px" src="https://heyapi.dev/scalar-logo-wordmark.svg"> 77 + </picture> 78 + </a> 79 + <br/> 80 + <a href="https://kutt.it/skQUVd" style="text-decoration:none;" target="_blank"> 81 + scalar.com 82 + </a> 83 + </td> 84 + <td align="center"> 85 + <a href="https://kutt.it/Dr9GuW" target="_blank"> 86 + <picture height="40px"> 87 + <img alt="FastAPI logo" height="40px" src="https://heyapi.dev/fastapi-logo-wordmark.svg"> 88 + </picture> 89 + </a> 90 + <br/> 91 + <a href="https://kutt.it/Dr9GuW" style="text-decoration:none;" target="_blank"> 92 + fastapi.tiangolo.com 93 + </a> 94 + </td> 95 + </tr> 96 + </tbody> 97 + </table> 98 + 99 + <h3 align="center">Bronze</h3> 100 + 101 + <table align="center" style="justify-content: center;align-items: center;display: flex;"> 102 + <tbody> 103 + <tr> 104 + <td align="center"> 105 + <a href="https://kutt.it/YpaKsX" target="_blank"> 106 + <picture height="34px"> 107 + <source media="(prefers-color-scheme: dark)" srcset="https://heyapi.dev/images/kinde-logo-wordmark-dark-480w.webp"> 108 + <img alt="Kinde logo" height="34px" src="https://heyapi.dev/images/kinde-logo-wordmark-480w.jpeg"> 109 + </picture> 110 + </a> 111 + </td> 112 + <td align="center"> 113 + <a href="https://kutt.it/KkqSaw" target="_blank"> 114 + <picture height="34px"> 115 + <source media="(prefers-color-scheme: dark)" srcset="https://heyapi.dev/images/cella-logo-wordmark-480w.webp"> 116 + <img alt="Cella logo" height="34px" src="https://heyapi.dev/cella-logo-wordmark.svg"> 117 + </picture> 118 + </a> 119 + </td> 120 + </tr> 121 + </tbody> 122 + </table> 123 + 124 + ## License 125 + 126 + Released under the [MIT License](https://github.com/hey-api/openapi-ts/blob/main/LICENSE.md).
+71
packages/codegen-core/package.json
··· 1 + { 2 + "name": "@hey-api/codegen-core", 3 + "version": "0.0.0", 4 + "description": "🧱 TypeScript framework for generating structured, multi-file source code from abstract syntax trees.", 5 + "homepage": "https://heyapi.dev/", 6 + "repository": { 7 + "type": "git", 8 + "url": "git+https://github.com/hey-api/openapi-ts.git" 9 + }, 10 + "bugs": { 11 + "url": "https://github.com/hey-api/openapi-ts/issues" 12 + }, 13 + "license": "MIT", 14 + "author": { 15 + "email": "lubos@heyapi.dev", 16 + "name": "Hey API", 17 + "url": "https://heyapi.dev" 18 + }, 19 + "funding": "https://github.com/sponsors/hey-api", 20 + "keywords": [ 21 + "codegen", 22 + "generator", 23 + "javascript", 24 + "node", 25 + "typescript" 26 + ], 27 + "type": "module", 28 + "main": "./dist/index.cjs", 29 + "module": "./dist/index.js", 30 + "types": "./dist/index.d.ts", 31 + "exports": { 32 + ".": { 33 + "import": { 34 + "types": "./dist/index.d.ts", 35 + "default": "./dist/index.js" 36 + }, 37 + "require": { 38 + "types": "./dist/index.d.cts", 39 + "default": "./dist/index.cjs" 40 + } 41 + }, 42 + "./package.json": "./package.json" 43 + }, 44 + "files": [ 45 + "dist", 46 + "LICENSE.md" 47 + ], 48 + "scripts": { 49 + "build": "tsup && pnpm check-exports", 50 + "check-exports": "attw --pack . --profile node16", 51 + "dev": "tsup --watch", 52 + "prepublishOnly": "pnpm build", 53 + "test:coverage": "vitest run --coverage", 54 + "test:update": "vitest watch --update", 55 + "test:watch": "vitest watch", 56 + "test": "vitest run", 57 + "typecheck": "tsc --noEmit" 58 + }, 59 + "engines": { 60 + "node": "^18.18.0 || ^20.9.0 || >=22.10.0" 61 + }, 62 + "peerDependencies": { 63 + "typescript": "^5.5.3" 64 + }, 65 + "devDependencies": { 66 + "@config/vite-base": "workspace:*", 67 + "eslint": "9.17.0", 68 + "prettier": "3.4.2", 69 + "typescript": "5.8.3" 70 + } 71 + }
+221
packages/codegen-core/src/__tests__/file.test.ts
··· 1 + import { beforeEach, describe, expect, it } from 'vitest'; 2 + 3 + import { CodegenFile } from '../files/file'; 4 + import type { ICodegenImport } from '../imports/types'; 5 + import type { ICodegenSymbol } from '../symbols/types'; 6 + 7 + describe('CodegenFile', () => { 8 + let file: CodegenFile; 9 + 10 + beforeEach(() => { 11 + file = new CodegenFile('a.ts'); 12 + }); 13 + 14 + it('initializes with empty imports and symbols', () => { 15 + expect(file.imports).toEqual([]); 16 + expect(file.symbols).toEqual([]); 17 + }); 18 + 19 + it('adds exports', () => { 20 + const exp1: ICodegenImport = { from: 'a' }; 21 + const exp2: ICodegenImport = { from: 'b' }; 22 + 23 + file.addExport(exp1); 24 + file.addExport(exp2); 25 + 26 + expect(file.exports.length).toBe(2); 27 + expect(file.exports[0]).not.toBe(exp1); 28 + expect(file.exports[0]).toEqual(exp1); 29 + expect(file.exports[1]).not.toBe(exp2); 30 + expect(file.exports[1]).toEqual(exp2); 31 + }); 32 + 33 + it('merges duplicate exports', () => { 34 + const exp1: ICodegenImport = { 35 + aliases: { 36 + A: 'AliasA', 37 + }, 38 + from: 'a', 39 + names: ['A'], 40 + typeNames: ['AType'], 41 + }; 42 + const exp2: ICodegenImport = { 43 + aliases: { 44 + B: 'AliasB', 45 + }, 46 + from: 'a', 47 + names: ['B'], 48 + typeNames: ['AType'], 49 + }; 50 + 51 + file.addExport(exp1); 52 + file.addExport(exp2); 53 + 54 + expect(file.exports.length).toBe(1); 55 + expect(file.exports[0]).toEqual({ 56 + aliases: { 57 + A: 'AliasA', 58 + B: 'AliasB', 59 + }, 60 + from: 'a', 61 + names: ['A', 'B'], 62 + typeNames: ['AType'], 63 + }); 64 + }); 65 + 66 + it('adds imports', () => { 67 + const imp1: ICodegenImport = { from: 'a' }; 68 + const imp2: ICodegenImport = { from: 'b' }; 69 + 70 + file.addImport(imp1); 71 + file.addImport(imp2); 72 + 73 + expect(file.imports.length).toBe(2); 74 + expect(file.imports[0]).not.toBe(imp1); 75 + expect(file.imports[0]).toEqual(imp1); 76 + expect(file.imports[1]).not.toBe(imp2); 77 + expect(file.imports[1]).toEqual(imp2); 78 + }); 79 + 80 + it('merges duplicate imports', () => { 81 + const imp1: ICodegenImport = { 82 + aliases: { 83 + A: 'AliasA', 84 + }, 85 + from: 'a', 86 + names: ['A'], 87 + typeNames: ['AType'], 88 + }; 89 + const imp2: ICodegenImport = { 90 + aliases: { 91 + B: 'AliasB', 92 + }, 93 + from: 'a', 94 + names: ['B'], 95 + typeNames: ['AType'], 96 + }; 97 + 98 + file.addImport(imp1); 99 + file.addImport(imp2); 100 + 101 + expect(file.imports.length).toBe(1); 102 + expect(file.imports[0]).toEqual({ 103 + aliases: { 104 + A: 'AliasA', 105 + B: 'AliasB', 106 + }, 107 + from: 'a', 108 + names: ['A', 'B'], 109 + typeNames: ['AType'], 110 + }); 111 + }); 112 + 113 + it('adds symbols', () => { 114 + const sym1: ICodegenSymbol = { name: 'a' }; 115 + const sym2: ICodegenSymbol = { name: 'b' }; 116 + 117 + file.addSymbol(sym1); 118 + file.addSymbol(sym2); 119 + 120 + expect(file.symbols.length).toBe(2); 121 + expect(file.symbols[0]).not.toBe(sym1); 122 + expect(file.symbols[0]).toEqual(sym1); 123 + expect(file.symbols[1]).not.toBe(sym2); 124 + expect(file.symbols[1]).toEqual(sym2); 125 + }); 126 + 127 + it('merges duplicate symbols', () => { 128 + const sym1: ICodegenSymbol = { 129 + name: 'a', 130 + value: 1, 131 + }; 132 + const sym2: ICodegenSymbol = { 133 + name: 'a', 134 + value: 'foo', 135 + }; 136 + 137 + file.addSymbol(sym1); 138 + file.addSymbol(sym2); 139 + 140 + expect(file.symbols.length).toBe(1); 141 + expect(file.symbols[0]).toEqual({ 142 + name: 'a', 143 + value: 'foo', 144 + }); 145 + }); 146 + 147 + it('getAllSymbols returns declared symbols plus imports and exports with alias applied', () => { 148 + file.addSymbol({ name: 'Local', value: {} }); 149 + file.addImport({ 150 + aliases: { A: 'AliasA' }, 151 + from: 'lib', 152 + names: ['A', 'B'], 153 + }); 154 + file.addExport({ 155 + aliases: { C: 'AliasC' }, 156 + from: 'lib', 157 + names: ['C', 'D'], 158 + }); 159 + 160 + const all = file.getAllSymbols().map((s) => s.name); 161 + expect(all).toEqual(['Local', 'AliasA', 'B', 'AliasC', 'D']); 162 + }); 163 + 164 + it('hasSymbol returns true if symbol exists', () => { 165 + file.addSymbol({ name: 'Exists', value: {} }); 166 + expect(file.hasSymbol('Exists')).toBe(true); 167 + expect(file.hasSymbol('Missing')).toBe(false); 168 + }); 169 + 170 + it('imports, exports, and symbols getters cache arrays and update after add', () => { 171 + expect(file.exports).toEqual([]); 172 + expect(file.imports).toEqual([]); 173 + expect(file.symbols).toEqual([]); 174 + 175 + const imp = { from: 'lib', names: ['X'] }; 176 + const symbol = { name: 'Sym', value: {} }; 177 + 178 + file.addExport(imp); 179 + expect(file.exports).toEqual([imp]); 180 + 181 + file.addImport(imp); 182 + expect(file.imports).toEqual([imp]); 183 + 184 + file.addSymbol(symbol); 185 + expect(file.symbols).toEqual([symbol]); 186 + }); 187 + 188 + it('returns relative path to another files', () => { 189 + const path1 = file.relativePathToFile({ path: 'b' }); 190 + expect(path1).toBe('./b'); 191 + 192 + const path2 = file.relativePathToFile({ path: './b' }); 193 + expect(path2).toBe('./b'); 194 + 195 + const path3 = file.relativePathToFile({ path: '../b' }); 196 + expect(path3).toBe('../b'); 197 + 198 + const path4 = file.relativePathToFile({ path: '../../b' }); 199 + expect(path4).toBe('../../b'); 200 + 201 + const path5 = file.relativePathToFile({ path: 'b/c' }); 202 + expect(path5).toBe('./b/c'); 203 + }); 204 + 205 + it('returns relative path to this file', () => { 206 + const path1 = file.relativePathFromFile({ path: 'b' }); 207 + expect(path1).toBe('./a.ts'); 208 + 209 + const path2 = file.relativePathFromFile({ path: './b' }); 210 + expect(path2).toBe('./a.ts'); 211 + 212 + const path3 = file.relativePathFromFile({ path: '../b' }); 213 + expect(path3).toBe('./codegen-core/a.ts'); 214 + 215 + const path4 = file.relativePathFromFile({ path: '../../b' }); 216 + expect(path4).toBe('./packages/codegen-core/a.ts'); 217 + 218 + const path5 = file.relativePathFromFile({ path: 'b/c' }); 219 + expect(path5).toBe('../a.ts'); 220 + }); 221 + });
+125
packages/codegen-core/src/__tests__/project.test.ts
··· 1 + import { beforeEach, describe, expect, it } from 'vitest'; 2 + 3 + import { CodegenFile } from '../files/file'; 4 + import type { ICodegenMeta } from '../meta/types'; 5 + import type { ICodegenOutput } from '../output/types'; 6 + import { CodegenProject } from '../project/project'; 7 + import type { ICodegenRenderer } from '../renderers/types'; 8 + 9 + describe('CodegenProject', () => { 10 + let project: CodegenProject; 11 + 12 + beforeEach(() => { 13 + project = new CodegenProject(); 14 + }); 15 + 16 + it('adds new files and preserves order', () => { 17 + const file1 = project.createFile('a.ts'); 18 + const file2 = project.createFile('b.ts'); 19 + 20 + expect(project.files).toEqual([file1, file2]); 21 + }); 22 + 23 + it('replaces existing file but keeps order', () => { 24 + project.createFile('a.ts'); 25 + project.createFile('b.ts'); 26 + project.createFile('c.ts'); 27 + const newFile2 = project.createFile('b.ts'); 28 + 29 + expect(project.files.length).toBe(3); 30 + expect(project.files[1]).toBe(newFile2); 31 + expect(project.getFileByPath('b.ts')).toBe(newFile2); 32 + }); 33 + 34 + it('addExportToFile creates file if missing and adds export', () => { 35 + const imp = { from: 'lib', names: ['Foo'] }; 36 + 37 + project.addExportToFile('a.ts', imp); 38 + 39 + const file = project.getFileByPath('a.ts')!; 40 + expect(file).toBeDefined(); 41 + expect(file.exports.length).toBe(1); 42 + expect(file.exports[0]).toEqual(imp); 43 + }); 44 + 45 + it('addImportToFile creates file if missing and adds import', () => { 46 + const imp = { from: 'lib', names: ['Foo'] }; 47 + 48 + project.addImportToFile('a.ts', imp); 49 + 50 + const file = project.getFileByPath('a.ts')!; 51 + expect(file).toBeDefined(); 52 + expect(file.imports.length).toBe(1); 53 + expect(file.imports[0]).toEqual(imp); 54 + }); 55 + 56 + it('addSymbolToFile creates file if missing and adds symbol', () => { 57 + const symbol = { name: 'MySymbol', value: {} }; 58 + 59 + project.addSymbolToFile('a.ts', symbol); 60 + 61 + const file = project.getFileByPath('a.ts')!; 62 + expect(file).toBeDefined(); 63 + expect(file.symbols.length).toBe(1); 64 + expect(file.symbols[0]).toEqual(symbol); 65 + }); 66 + 67 + it('getAllSymbols returns all symbols from all files', () => { 68 + const file1 = project.createFile('a.ts'); 69 + const file2 = project.createFile('b.ts'); 70 + 71 + file1.addSymbol({ name: 'A', value: {} }); 72 + file2.addSymbol({ name: 'B', value: {} }); 73 + 74 + const symbols = project.getAllSymbols().map((s) => s.name); 75 + expect(symbols).toEqual(['A', 'B']); 76 + }); 77 + 78 + it('getFileByPath returns undefined for unknown path', () => { 79 + expect(project.getFileByPath('unknown.ts')).toBeUndefined(); 80 + }); 81 + 82 + it('files getter returns a copy of the files array', () => { 83 + const file = project.createFile('a.ts'); 84 + 85 + const files = project.files; 86 + expect(files).toEqual([file]); 87 + 88 + // @ts-expect-error 89 + // mutate returned array should not affect internal state 90 + files.push(new CodegenFile('b.ts')); 91 + expect(project.files).toEqual([file]); 92 + }); 93 + 94 + it('render returns output from all files', () => { 95 + class Renderer implements ICodegenRenderer { 96 + id = 'foo'; 97 + render(file: CodegenFile, meta?: ICodegenMeta): ICodegenOutput { 98 + return { 99 + content: `content ${file.path}`, 100 + meta: { ...meta }, 101 + path: file.path, 102 + }; 103 + } 104 + } 105 + const renderer = new Renderer(); 106 + const meta = { foo: 42 }; 107 + project.createFile('a.ts', { renderer }); 108 + project.createFile('b.ts', { renderer }); 109 + 110 + const outputs = project.render(meta); 111 + expect(outputs).toEqual([ 112 + { content: 'content a.ts', meta: { foo: 42 }, path: 'a.ts' }, 113 + { content: 'content b.ts', meta: { foo: 42 }, path: 'b.ts' }, 114 + ]); 115 + }); 116 + 117 + it('createFile adds and returns a new file with optional meta', () => { 118 + const file = project.createFile('a.ts', { extension: '.ts' }); 119 + 120 + expect(file.path).toBe('a.ts'); 121 + expect(file.meta).toEqual({ extension: '.ts' }); 122 + expect(project.getFileByPath('a.ts')).toBe(file); 123 + expect(project.files).toEqual([file]); 124 + }); 125 + });
+175
packages/codegen-core/src/files/file.ts
··· 1 + import path from 'node:path'; 2 + 3 + import type { ICodegenImport } from '../imports/types'; 4 + import type { ICodegenSymbol } from '../symbols/types'; 5 + import type { ICodegenFile } from './types'; 6 + 7 + export class CodegenFile implements ICodegenFile { 8 + private cache: { 9 + exports?: ReadonlyArray<ICodegenImport>; 10 + imports?: ReadonlyArray<ICodegenImport>; 11 + symbols?: ReadonlyArray<ICodegenSymbol>; 12 + } = {}; 13 + 14 + private state: { 15 + exports: Map<string, ICodegenImport>; 16 + imports: Map<string, ICodegenImport>; 17 + symbols: Map<string, ICodegenSymbol>; 18 + } = { 19 + exports: new Map(), 20 + imports: new Map(), 21 + symbols: new Map(), 22 + }; 23 + 24 + constructor( 25 + public path: string, 26 + public meta: ICodegenFile['meta'] = {}, 27 + ) { 28 + let filePath = CodegenFile.pathToFilePath(path); 29 + if (meta.path) { 30 + if (typeof meta.path === 'function') { 31 + filePath = meta.path(filePath); 32 + } else { 33 + filePath = meta.path.replace('{{path}}', filePath); 34 + } 35 + } 36 + this.path = filePath; 37 + } 38 + 39 + addExport(exp: ICodegenImport): void { 40 + return this.addImportExport(exp, 'exports'); 41 + } 42 + 43 + addImport(imp: ICodegenImport): void { 44 + return this.addImportExport(imp, 'imports'); 45 + } 46 + 47 + private addImportExport( 48 + value: ICodegenImport, 49 + field: 'exports' | 'imports', 50 + ): void { 51 + const key = typeof value.from === 'string' ? value.from : value.from.path; 52 + const existing = this.state[field].get(key); 53 + if (existing) { 54 + this.mergeImportExportValues(existing, value); 55 + this.state[field].set(key, existing); 56 + } else { 57 + this.state[field].set(key, { ...value }); // clone to avoid mutation 58 + } 59 + this.cache[field] = undefined; 60 + } 61 + 62 + addSymbol(symbol: ICodegenSymbol): void { 63 + const key = symbol.name; 64 + const existing = this.state.symbols.get(key); 65 + if (existing) { 66 + existing.value = symbol.value; 67 + this.state.symbols.set(key, existing); 68 + } else { 69 + this.state.symbols.set(key, { ...symbol }); // clone to avoid mutation 70 + } 71 + this.cache.symbols = undefined; 72 + } 73 + 74 + get exports(): ReadonlyArray<ICodegenImport> { 75 + if (!this.cache.exports) { 76 + this.cache.exports = Array.from(this.state.exports.values()); 77 + } 78 + return this.cache.exports; 79 + } 80 + 81 + getAllSymbols(): ReadonlyArray<ICodegenSymbol> { 82 + return [ 83 + ...this.symbols, 84 + ...this.imports.flatMap((imp) => 85 + (imp.names ?? []).map((name) => ({ 86 + name: imp.aliases?.[name] ?? name, 87 + })), 88 + ), 89 + ...this.exports.flatMap((imp) => 90 + (imp.names ?? []).map((name) => ({ 91 + name: imp.aliases?.[name] ?? name, 92 + })), 93 + ), 94 + ]; 95 + } 96 + 97 + hasContent(): boolean { 98 + return this.state.symbols.size > 0 || this.state.exports.size > 0; 99 + } 100 + 101 + hasSymbol(name: string): boolean { 102 + return this.state.symbols.has(name); 103 + } 104 + 105 + get imports(): ReadonlyArray<ICodegenImport> { 106 + if (!this.cache.imports) { 107 + this.cache.imports = Array.from(this.state.imports.values()); 108 + } 109 + return this.cache.imports; 110 + } 111 + 112 + private mergeImportExportValues( 113 + target: ICodegenImport, 114 + source: ICodegenImport, 115 + ): void { 116 + target.aliases = { ...target.aliases, ...source.aliases }; 117 + if (source.defaultImport !== undefined) { 118 + target.defaultImport = source.defaultImport; 119 + } 120 + target.names = [ 121 + ...new Set([...(target.names ?? []), ...(source.names ?? [])]), 122 + ]; 123 + if (source.namespaceImport !== undefined) { 124 + target.namespaceImport = source.namespaceImport; 125 + } 126 + if (source.typeDefaultImport !== undefined) { 127 + target.typeDefaultImport = source.typeDefaultImport; 128 + } 129 + target.typeNames = [ 130 + ...new Set([...(target.typeNames ?? []), ...(source.typeNames ?? [])]), 131 + ]; 132 + if (source.typeNamespaceImport !== undefined) { 133 + target.typeNamespaceImport = source.typeNamespaceImport; 134 + } 135 + } 136 + 137 + static pathToFilePath(source: string): string { 138 + if (source.includes('/')) { 139 + return source.split('/').filter(Boolean).join(path.sep); 140 + } 141 + if (source.includes('\\')) { 142 + return source.split('\\').filter(Boolean).join(path.sep); 143 + } 144 + return source.split(path.sep).filter(Boolean).join(path.sep); 145 + } 146 + 147 + relativePathFromFile(file: Pick<ICodegenFile, 'path'>): string { 148 + let relativePath = path.posix.relative( 149 + path.posix.dirname(file.path), 150 + this.path, 151 + ); 152 + if (!relativePath.startsWith('.')) { 153 + relativePath = `./${relativePath}`; 154 + } 155 + return relativePath; 156 + } 157 + 158 + relativePathToFile(file: Pick<ICodegenFile, 'path'>): string { 159 + let relativePath = path.posix.relative( 160 + path.posix.dirname(this.path), 161 + file.path, 162 + ); 163 + if (!relativePath.startsWith('.')) { 164 + relativePath = `./${relativePath}`; 165 + } 166 + return relativePath; 167 + } 168 + 169 + get symbols(): ReadonlyArray<ICodegenSymbol> { 170 + if (!this.cache.symbols) { 171 + this.cache.symbols = Array.from(this.state.symbols.values()); 172 + } 173 + return this.cache.symbols; 174 + } 175 + }
+109
packages/codegen-core/src/files/types.d.ts
··· 1 + import type { ICodegenImport } from '../imports/types'; 2 + import type { ICodegenRenderer } from '../renderers/types'; 3 + import type { ICodegenSymbol } from '../symbols/types'; 4 + 5 + export interface ICodegenFile { 6 + /** 7 + * Adds an export to this file. 8 + * 9 + * This is also known as a re-export. 10 + * 11 + * @param exp The export to add 12 + */ 13 + addExport(exp: ICodegenImport): void; 14 + /** 15 + * Adds an import to this file. 16 + * 17 + * @param imp The import to add 18 + */ 19 + addImport(imp: ICodegenImport): void; 20 + /** 21 + * Adds a symbol defined by this file. 22 + * 23 + * @param symbol The symbol to add 24 + */ 25 + addSymbol(symbol: ICodegenSymbol): void; 26 + /** 27 + * Symbols exported from other files. 28 + **/ 29 + exports: ReadonlyArray<ICodegenImport>; 30 + /** 31 + * Returns all symbols used in this file (declared + imported). 32 + * 33 + * @returns List of all symbols used in this file 34 + */ 35 + getAllSymbols(): ReadonlyArray<ICodegenSymbol>; 36 + /** 37 + * Checks if this file contains any content. 38 + * 39 + * This is used to determine whether we want to process the file further. 40 + * By default, we consider only symbols and exports as content. 41 + * 42 + * @returns True if the file contains content 43 + */ 44 + hasContent(): boolean; 45 + /** 46 + * Checks if this file defines a symbol with the given name. 47 + * 48 + * @param name Symbol name to check 49 + * @returns True if the symbol is defined by this file 50 + */ 51 + hasSymbol(name: string): boolean; 52 + /** 53 + * Symbols imported from other files. 54 + **/ 55 + imports: ReadonlyArray<ICodegenImport>; 56 + /** 57 + * Optional metadata about the file. 58 + **/ 59 + meta: { 60 + /** 61 + * Optional file extension. 62 + * 63 + * @example ".ts" 64 + */ 65 + extension?: '.ts' | (string & {}); 66 + /** 67 + * Optional logical module or package name. 68 + * 69 + * @example "models.user" 70 + */ 71 + moduleName?: string; 72 + /** 73 + * Optional path transformer. 74 + * 75 + * @param path Original file path passed to the constructor. 76 + */ 77 + path?: ((path: string) => string) | string; 78 + /** 79 + * Renderer ID. 80 + * 81 + * @example "typescript" 82 + */ 83 + renderer?: ICodegenRenderer['id']; 84 + }; 85 + /** 86 + * Logical output path (used for writing the file). 87 + * 88 + * @example "models/user.ts" 89 + */ 90 + path: string; 91 + /** 92 + * Returns a relative path to this file from another file. 93 + * 94 + * @param file The file from which we want the relative path to this file. 95 + * @example "./this-file.ts" 96 + */ 97 + relativePathFromFile(file: Pick<ICodegenFile, 'path'>): string; 98 + /** 99 + * Returns a relative path to file from this file. 100 + * 101 + * @param file The file to which we want the relative path. 102 + * @example "./another-file.ts" 103 + */ 104 + relativePathToFile(file: Pick<ICodegenFile, 'path'>): string; 105 + /** 106 + * Top-level symbols declared in this file. 107 + **/ 108 + symbols: ReadonlyArray<ICodegenSymbol>; 109 + }
+64
packages/codegen-core/src/imports/types.d.ts
··· 1 + import type { ICodegenFile } from '../files/types'; 2 + 3 + export interface ICodegenImport { 4 + /** 5 + * Optional aliasing map for imported symbols. 6 + * 7 + * Keys must be a subset of `names`, values are aliases. 8 + * 9 + * @example { User: "ImportedUser" } 10 + */ 11 + aliases?: Record<string, string>; 12 + /** 13 + * Name of the default import, if any. 14 + * 15 + * @example "React" 16 + */ 17 + defaultImport?: string; 18 + /** 19 + * Source file or external module from which symbols are imported. 20 + * 21 + * For internal files, this should be a ICodegenFile instance to enable 22 + * dynamic path computation. For external or system modules, use a string. 23 + * 24 + * @example "./models/user" 25 + * @example "node:path" 26 + */ 27 + from: ICodegenFile | string; 28 + /** 29 + * Names of the symbols imported from the source. 30 + * 31 + * Must be non-empty unless `isNamespaceImport` is true. 32 + * All imported names, regardless of whether they are used as types or values. 33 + * 34 + * @example ["User", "UserDTO"] 35 + */ 36 + names?: ReadonlyArray<string>; 37 + /** 38 + * If this import is a namespace import (e.g. `import * as ns from "..."`), 39 + * this should be the namespace alias. Set to `true` if no alias is needed. 40 + * 41 + * @example "utils" 42 + * @example true 43 + */ 44 + namespaceImport?: boolean | string; 45 + /** 46 + * Whether the default import is type-only. 47 + * 48 + * @example true 49 + */ 50 + typeDefaultImport?: boolean; 51 + /** 52 + * Subset of `names` that are imported using the `type` modifier. 53 + * These symbols will be emitted as type-only imports in TypeScript. 54 + * 55 + * @example ["UserDTO"] 56 + */ 57 + typeNames?: ReadonlyArray<string>; 58 + /** 59 + * Whether the namespace import is type-only. 60 + * 61 + * @example true 62 + */ 63 + typeNamespaceImport?: boolean; 64 + }
+9
packages/codegen-core/src/index.ts
··· 1 + export { CodegenFile } from './files/file'; 2 + export type { ICodegenFile } from './files/types'; 3 + export type { ICodegenImport } from './imports/types'; 4 + export type { ICodegenMeta } from './meta/types'; 5 + export type { ICodegenOutput } from './output/types'; 6 + export { CodegenProject } from './project/project'; 7 + export type { ICodegenProject } from './project/types'; 8 + export type { ICodegenRenderer } from './renderers/types'; 9 + export type { ICodegenSymbol } from './symbols/types';
+8
packages/codegen-core/src/meta/types.d.ts
··· 1 + /** 2 + * Arbitrary metadata passed to render functions. 3 + * 4 + * Implementors should extend this interface for their own needs. 5 + */ 6 + export interface ICodegenMeta { 7 + [key: string]: unknown; 8 + }
+23
packages/codegen-core/src/output/types.d.ts
··· 1 + export interface ICodegenOutput { 2 + /** 3 + * The main content of the file to output. 4 + * 5 + * A raw string representing source code. 6 + * 7 + * @example "function foo(): void {\n // implementation\n}\n" 8 + */ 9 + content: string; 10 + /** 11 + * Optional metadata or hints for the emitter, such as formatting options, 12 + * source maps, or language-specific flags. 13 + * 14 + * @example { format: "prettier", sourceMap: true } 15 + */ 16 + meta: Record<string, unknown>; 17 + /** 18 + * Logical output path (used for writing the file). 19 + * 20 + * @example "models/user.ts" 21 + */ 22 + path: string; 23 + }
+104
packages/codegen-core/src/project/project.ts
··· 1 + import { CodegenFile } from '../files/file'; 2 + import type { ICodegenImport } from '../imports/types'; 3 + import type { ICodegenMeta } from '../meta/types'; 4 + import type { ICodegenOutput } from '../output/types'; 5 + import type { ICodegenRenderer } from '../renderers/types'; 6 + import type { ICodegenSymbol } from '../symbols/types'; 7 + import type { ICodegenProject } from './types'; 8 + 9 + export class CodegenProject implements ICodegenProject { 10 + private filesMap: Map<string, CodegenFile> = new Map(); 11 + private filesOrder: Array<CodegenFile> = []; 12 + private renderers: Map<string, ICodegenRenderer> = new Map(); 13 + 14 + addExportToFile(fileOrPath: CodegenFile | string, imp: ICodegenImport): void { 15 + const file = this.ensureFile(fileOrPath); 16 + file.addExport(imp); 17 + } 18 + 19 + addImportToFile(fileOrPath: CodegenFile | string, imp: ICodegenImport): void { 20 + const file = this.ensureFile(fileOrPath); 21 + file.addImport(imp); 22 + } 23 + 24 + addSymbolToFile( 25 + fileOrPath: CodegenFile | string, 26 + symbol: ICodegenSymbol, 27 + ): void { 28 + const file = this.ensureFile(fileOrPath); 29 + file.addSymbol(symbol); 30 + } 31 + 32 + createFile( 33 + path: string, 34 + meta: Omit<CodegenFile['meta'], 'renderer'> & { 35 + /** 36 + * Renderer to use to render this file. 37 + */ 38 + renderer?: ICodegenRenderer; 39 + } = {}, 40 + ): CodegenFile { 41 + const { renderer, ...metadata } = meta; 42 + if (renderer) { 43 + this.ensureRenderer(renderer); 44 + } 45 + 46 + const existing = this.getFileByPath(path); 47 + if (existing) { 48 + // Whoever is creating the file will override the renderer 49 + if (renderer?.id && renderer.id !== existing.meta.renderer) { 50 + existing.meta.renderer = renderer.id; 51 + } 52 + return existing; 53 + } 54 + 55 + const file = new CodegenFile(path, { 56 + ...metadata, 57 + renderer: renderer?.id, 58 + }); 59 + this.filesOrder.push(file); 60 + this.filesMap.set(path, file); 61 + return file; 62 + } 63 + 64 + ensureFile(fileOrPath: CodegenFile | string): CodegenFile { 65 + if (typeof fileOrPath !== 'string') { 66 + return fileOrPath; 67 + } 68 + const existingFile = this.getFileByPath(fileOrPath); 69 + if (existingFile) { 70 + return existingFile; 71 + } 72 + return this.createFile(fileOrPath); 73 + } 74 + 75 + private ensureRenderer(renderer: ICodegenRenderer): ICodegenRenderer { 76 + if (!this.renderers.has(renderer.id)) { 77 + this.renderers.set(renderer.id, renderer); 78 + } 79 + return this.renderers.get(renderer.id)!; 80 + } 81 + 82 + get files(): ReadonlyArray<CodegenFile> { 83 + return [...this.filesOrder]; 84 + } 85 + 86 + getAllSymbols(): ReadonlyArray<ICodegenSymbol> { 87 + return this.filesOrder.flatMap((file) => file.getAllSymbols()); 88 + } 89 + 90 + getFileByPath(path: string): CodegenFile | undefined { 91 + return this.filesMap.get(path); 92 + } 93 + 94 + render(meta?: ICodegenMeta): ReadonlyArray<ICodegenOutput> { 95 + const results: Array<ICodegenOutput> = []; 96 + for (const file of this.filesOrder) { 97 + if (!file.meta.renderer) continue; 98 + const renderer = this.renderers.get(file.meta.renderer); 99 + if (!renderer) continue; 100 + results.push(renderer.render(file, meta)); 101 + } 102 + return results; 103 + } 104 + }
+104
packages/codegen-core/src/project/types.d.ts
··· 1 + import type { ICodegenFile } from '../files/types'; 2 + import type { ICodegenImport } from '../imports/types'; 3 + import type { ICodegenMeta } from '../meta/types'; 4 + import type { ICodegenOutput } from '../output/types'; 5 + import type { ICodegenRenderer } from '../renderers/types'; 6 + import type { ICodegenSymbol } from '../symbols/types'; 7 + 8 + /** 9 + * Represents a code generation project consisting of multiple codegen files. 10 + * Manages imports, symbols, and output generation across the project. 11 + */ 12 + export interface ICodegenProject { 13 + /** 14 + * Adds an export declaration to a specific file, creating the file if it doesn't exist. 15 + * 16 + * @param fileOrPath - File instance or file path where to add the export. 17 + * @param imp - The export declaration to add. 18 + * @example 19 + * project.addExportToFile("models/user.ts", { from: "lib", names: ["User"] }); 20 + */ 21 + addExportToFile(fileOrPath: ICodegenFile | string, imp: ICodegenImport): void; 22 + /** 23 + * Adds an import declaration to a specific file, creating the file if it doesn't exist. 24 + * 25 + * @param fileOrPath - File instance or file path where to add the import. 26 + * @param imp - The import declaration to add. 27 + * @example 28 + * project.addImportToFile("models/user.ts", { from: "lib", names: ["User"] }); 29 + */ 30 + addImportToFile(fileOrPath: ICodegenFile | string, imp: ICodegenImport): void; 31 + /** 32 + * Adds a symbol to a specific file, creating the file if it doesn't exist. 33 + * 34 + * @param fileOrPath - File instance or file path where to add the symbol. 35 + * @param symbol - The symbol to add. 36 + * @example 37 + * project.addSymbolToFile("models/user.ts", { name: "User", value: tsNode }); 38 + */ 39 + addSymbolToFile( 40 + fileOrPath: ICodegenFile | string, 41 + symbol: ICodegenSymbol, 42 + ): void; 43 + /** 44 + * Creates a new codegen file with optional metadata and adds it to the project. 45 + * 46 + * If a file with the same path already exists, it is returned instead. 47 + * 48 + * @param path - The logical output path for the file (e.g. "models/user.ts"). 49 + * @param meta - Optional renderer and metadata to attach to the file (e.g. { isInternal: true }). 50 + * @returns The newly created file instance. 51 + * @example 52 + * const file = project.createFile("models/user.ts", { isInternal: true }); 53 + */ 54 + createFile( 55 + path: string, 56 + meta?: ICodegenFile['meta'] & { renderer?: ICodegenRenderer }, 57 + ): ICodegenFile; 58 + /** 59 + * Ensures a codegen file exists and returns it. 60 + * 61 + * If a file does not exist yet, it is created with minimal information. 62 + * Later, it is expected `createFile()` will be called which will fill in 63 + * the missing information such as optional metadata. 64 + * 65 + * @param fileOrPath - The logical output path for the file or the file itself. 66 + * @returns The file instance. 67 + * @example 68 + * const file = project.ensureFile("models/user.ts"); 69 + */ 70 + ensureFile(fileOrPath: ICodegenFile | string): ICodegenFile; 71 + /** 72 + * Returns all files in the project in insertion order. 73 + * 74 + * @example 75 + * project.files.forEach(file => console.log(file.path)); 76 + */ 77 + readonly files: ReadonlyArray<ICodegenFile>; 78 + /** 79 + * Returns all symbols declared or imported across all files. 80 + * 81 + * @returns Flattened list of all codegen symbols. 82 + * @example 83 + * project.getAllSymbols().filter(s => s.name === "User"); 84 + */ 85 + getAllSymbols(): ReadonlyArray<ICodegenSymbol>; 86 + /** 87 + * Retrieves a file by its logical output path. 88 + * 89 + * @param path - The file path to find. 90 + * @returns The file if found, or undefined otherwise. 91 + * @example 92 + * const file = project.getFileByPath("models/user.ts"); 93 + */ 94 + getFileByPath(path: string): ICodegenFile | undefined; 95 + /** 96 + * Produces output representations for all files in the project. 97 + * 98 + * @param meta Arbitrary metadata. 99 + * @returns Array of outputs ready for writing or further processing. 100 + * @example 101 + * project.render().forEach(output => writeFile(output)); 102 + */ 103 + render(meta?: ICodegenMeta): ReadonlyArray<ICodegenOutput>; 104 + }
+24
packages/codegen-core/src/renderers/types.d.ts
··· 1 + import type { CodegenFile } from '../files/file'; 2 + import type { ICodegenMeta } from '../meta/types'; 3 + import type { ICodegenOutput } from '../output/types'; 4 + 5 + export interface ICodegenRenderer { 6 + /** 7 + * Optional: hook for renderer-level setup logic (e.g., formatting, config) 8 + */ 9 + configure?(options: Record<string, unknown>): void; 10 + /** 11 + * Unique identifier for this renderer. 12 + * 13 + * @example "typescript" 14 + */ 15 + id: string; 16 + /** 17 + * Returns printable data. 18 + * 19 + * @param file The file to render. 20 + * @param meta Arbitrary metadata. 21 + * @returns Output for file emit step 22 + */ 23 + render(file: CodegenFile, meta?: ICodegenMeta): ICodegenOutput; 24 + }
+25
packages/codegen-core/src/symbols/types.d.ts
··· 1 + export interface ICodegenSymbol { 2 + /** 3 + * Optional description or doc comment. 4 + * 5 + * @example "Represents a user in the system" 6 + */ 7 + description?: string; 8 + /** 9 + * Optional kind of symbol (e.g. "class", "function", "type", etc.). 10 + * 11 + * @example "class" 12 + */ 13 + kind?: string; 14 + /** 15 + * Unique identifier for the symbol within its file. 16 + * 17 + * @example "UserModel" 18 + */ 19 + name: string; 20 + /** 21 + * Internal representation of the symbol (e.g. AST node, IR object, raw 22 + * code). Used to generate output. 23 + */ 24 + value?: unknown; 25 + }
+16
packages/codegen-core/tsconfig.base.json
··· 1 + { 2 + "compilerOptions": { 3 + "declaration": true, 4 + "esModuleInterop": true, 5 + "module": "ESNext", 6 + "moduleResolution": "Bundler", 7 + "noImplicitOverride": true, 8 + "noImplicitReturns": true, 9 + "noUncheckedIndexedAccess": true, 10 + "noUnusedLocals": true, 11 + "noUnusedParameters": true, 12 + "strict": true, 13 + "target": "ES2022", 14 + "useUnknownInCatchVariables": false 15 + } 16 + }
+9
packages/codegen-core/tsconfig.json
··· 1 + { 2 + "extends": "./tsconfig.base.json", 3 + "compilerOptions": { 4 + "declaration": false, 5 + "esModuleInterop": true, 6 + "resolveJsonModule": true, 7 + "skipLibCheck": true 8 + } 9 + }
+25
packages/codegen-core/tsup.config.ts
··· 1 + import { defineConfig } from 'tsup'; 2 + 3 + export default defineConfig((options) => ({ 4 + banner(ctx) { 5 + /** 6 + * fix dynamic require in ESM 7 + * @link https://github.com/hey-api/openapi-ts/issues/1079 8 + */ 9 + if (ctx.format === 'esm') { 10 + return { 11 + js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);", 12 + }; 13 + } 14 + 15 + return; 16 + }, 17 + clean: true, 18 + dts: true, 19 + entry: ['src/index.ts'], 20 + format: ['cjs', 'esm'], 21 + minify: !options.watch, 22 + shims: false, 23 + sourcemap: true, 24 + treeshake: true, 25 + }));
+10
packages/codegen-core/turbo.json
··· 1 + { 2 + "$schema": "../../node_modules/turbo/schema.json", 3 + "extends": ["//"], 4 + "tasks": { 5 + "build": { 6 + "dependsOn": [], 7 + "outputs": ["dist/**"] 8 + } 9 + } 10 + }
+10
packages/codegen-core/vitest.config.ts
··· 1 + import { fileURLToPath } from 'node:url'; 2 + 3 + import { createVitestConfig } from '@config/vite-base'; 4 + 5 + export default createVitestConfig( 6 + fileURLToPath(new URL('./', import.meta.url)), 7 + { 8 + // Add specific configuration here if needed 9 + }, 10 + );
+16 -16
packages/openapi-ts-tests/main/test/openapi-ts.config.ts
··· 81 81 // deprecated: false, 82 82 operations: { 83 83 include: [ 84 - // 'GET /event', 84 + 'GET /event', 85 85 // '/^[A-Z]+ /v1//', 86 86 ], 87 87 }, ··· 229 229 { 230 230 // case: 'SCREAMING_SNAKE_CASE', 231 231 // comments: false, 232 - definitions: 'z{{name}}Definition', 232 + // definitions: 'z{{name}}Definition', 233 233 exportFromIndex: true, 234 - metadata: true, 235 - // name: 'valibot', 236 - requests: { 237 - // case: 'SCREAMING_SNAKE_CASE', 238 - name: 'z{{name}}TestData', 239 - }, 240 - responses: { 241 - // case: 'snake_case', 242 - name: 'z{{name}}TestResponse', 243 - }, 244 - webhooks: { 245 - name: 'q{{name}}CoolWebhook', 246 - }, 234 + // metadata: true, 235 + name: 'valibot', 236 + // requests: { 237 + // // case: 'SCREAMING_SNAKE_CASE', 238 + // name: 'z{{name}}TestData', 239 + // }, 240 + // responses: { 241 + // // case: 'snake_case', 242 + // name: 'z{{name}}TestResponse', 243 + // }, 244 + // webhooks: { 245 + // name: 'q{{name}}CoolWebhook', 246 + // }, 247 247 }, 248 248 { 249 249 // case: 'snake_case', ··· 261 261 }, 262 262 exportFromIndex: true, 263 263 metadata: true, 264 - // name: 'zod', 264 + name: 'zod', 265 265 // requests: { 266 266 // // case: 'SCREAMING_SNAKE_CASE', 267 267 // // name: 'z{{name}}TestData',
+1
packages/openapi-ts/package.json
··· 88 88 "node": "^18.18.0 || ^20.9.0 || >=22.10.0" 89 89 }, 90 90 "dependencies": { 91 + "@hey-api/codegen-core": "workspace:^0.0.0", 91 92 "@hey-api/json-schema-ref-parser": "1.0.7", 92 93 "ansi-colors": "4.1.3", 93 94 "c12": "2.0.1",
+2 -30
packages/openapi-ts/src/generate/file/index.ts
··· 140 140 return result; 141 141 } 142 142 143 - /** 144 - * Prevents a specific identifier from being created. This is useful for 145 - * transformers where we know a certain transformer won't be needed, and 146 - * we want to avoid attempting to create since we know it won't happen. 147 - */ 148 - public blockIdentifier({ 149 - $ref, 150 - namespace, 151 - }: Pick<EnsureUniqueIdentifierData, '$ref'> & { 152 - namespace: Namespace; 153 - }): Identifier { 154 - const { name, ref } = parseRef($ref); 155 - const refValue = 156 - this.identifiers[name.toLocaleLowerCase()]?.[namespace]?.[ref]; 157 - if (!refValue) { 158 - throw new Error( 159 - `Identifier for $ref ${$ref} in namespace ${namespace} not found`, 160 - ); 161 - } 162 - 163 - refValue.name = false; 164 - 165 - return { 166 - created: false, 167 - name: refValue.name, 168 - }; 169 - } 170 - 171 143 public get exportFromIndex(): boolean { 172 144 return this._exportFromIndex; 173 145 } ··· 469 441 } 470 442 } 471 443 472 - const parseRef = ( 444 + export const parseRef = ( 473 445 $ref: string, 474 446 ): { 475 447 /** ··· 528 500 return transformer.replace('{{name}}', `${separator}${name}${separator}`); 529 501 }; 530 502 531 - interface EnsureUniqueIdentifierData { 503 + export interface EnsureUniqueIdentifierData { 532 504 $ref: string; 533 505 case: StringCase | undefined; 534 506 count?: number;
+15 -1
packages/openapi-ts/src/generate/output.ts
··· 1 + import fs from 'node:fs'; 1 2 import path from 'node:path'; 2 3 3 4 import ts from 'typescript'; ··· 37 38 } 38 39 39 40 if (!context.config.dryRun) { 41 + // TODO: delete old approach 40 42 const indexFile = context.createFile({ 41 43 id: '_index', 42 44 path: 'index', 43 45 }); 44 46 47 + // TODO: delete old approach 45 48 for (const file of Object.values(context.files)) { 46 49 const fileName = file.nameWithoutExtension(); 47 50 ··· 76 79 file.write('\n\n', tsConfig); 77 80 } 78 81 79 - if (context.config.output.indexFile) { 82 + // TODO: delete old approach 83 + if (!indexFile.isEmpty()) { 80 84 indexFile.write('\n', tsConfig); 85 + } 86 + 87 + for (const file of context.gen.render({ 88 + moduleResolution: tsConfig?.options.moduleResolution, 89 + })) { 90 + if (!file.content) continue; 91 + const filePath = path.resolve(outputPath, file.path); 92 + const dir = path.dirname(filePath); 93 + fs.mkdirSync(dir, { recursive: true }); 94 + fs.writeFileSync(filePath, file.content, { encoding: 'utf8' }); 81 95 } 82 96 } 83 97 };
+216
packages/openapi-ts/src/generate/renderer.ts
··· 1 + import type { 2 + CodegenFile, 3 + ICodegenFile, 4 + ICodegenImport, 5 + ICodegenMeta, 6 + ICodegenOutput, 7 + ICodegenRenderer, 8 + } from '@hey-api/codegen-core'; 9 + import ts from 'typescript'; 10 + 11 + import { tsc } from '../tsc'; 12 + import { tsNodeToString } from '../tsc/utils'; 13 + 14 + export class TypeScriptRenderer implements ICodegenRenderer { 15 + id = 'typescript'; 16 + 17 + render(file: CodegenFile, meta?: ICodegenMeta): ICodegenOutput { 18 + const extension = file.meta.extension ?? ''; 19 + return { 20 + content: this.renderFile(file, meta), 21 + meta: file.meta, 22 + path: `${file.path}${extension}`, 23 + }; 24 + } 25 + 26 + private groupByKey( 27 + file: ICodegenFile, 28 + group: 'exports' | 'imports', 29 + meta?: ICodegenMeta, 30 + ): Map<string, Array<ICodegenImport>> { 31 + const grouped = new Map<string, Array<ICodegenImport>>(); 32 + 33 + for (const value of file[group]) { 34 + const shouldAppendJs = 35 + meta?.moduleResolution === ts.ModuleResolutionKind.NodeNext; 36 + const key = 37 + typeof value.from === 'string' 38 + ? value.from 39 + : `${file.relativePathToFile(value.from)}${shouldAppendJs ? '.js' : ''}`; 40 + if (!grouped.has(key)) { 41 + grouped.set(key, []); 42 + } 43 + grouped.get(key)!.push(value); 44 + } 45 + 46 + return grouped; 47 + } 48 + 49 + private renderFile(file: ICodegenFile, meta?: ICodegenMeta): string { 50 + if (!file.hasContent()) { 51 + return ''; 52 + } 53 + const result = [ 54 + this.renderHeaders(), 55 + this.renderImports(file, meta), 56 + this.renderSymbols(file), 57 + this.renderExports(file, meta), 58 + ] 59 + .filter(Boolean) 60 + .join('\n'); 61 + return result.endsWith('\n') ? result : `${result}\n`; 62 + } 63 + 64 + private renderHeaders(): string { 65 + return ['// This file is auto-generated by @hey-api/openapi-ts', ''].join( 66 + '\n', 67 + ); 68 + } 69 + 70 + private renderExports(file: ICodegenFile, meta?: ICodegenMeta): string { 71 + const grouped = this.groupByKey(file, 'exports', meta); 72 + const statements: Array<string> = []; 73 + 74 + for (const [from, group] of grouped.entries()) { 75 + const isTypeOnly = group.every( 76 + (value) => 77 + value.typeDefaultImport || 78 + value.typeNamespaceImport || 79 + value.names?.every((name) => value.typeNames?.includes(name)), 80 + ); 81 + 82 + if (group.length === 1 && group[0]!.namespaceImport) { 83 + const exportClause = 84 + typeof group[0]!.namespaceImport === 'string' 85 + ? ts.factory.createNamespaceExport( 86 + tsc.identifier({ text: group[0]!.namespaceImport }), 87 + ) 88 + : undefined; 89 + const node = ts.factory.createExportDeclaration( 90 + undefined, 91 + false, 92 + exportClause, 93 + tsc.stringLiteral({ isSingleQuote: true, text: from }), 94 + ); 95 + statements.push(tsNodeToString({ node })); 96 + continue; 97 + } 98 + 99 + const namedSpecifiers: Array<ts.ExportSpecifier> = []; 100 + for (const value of group) { 101 + for (const name of value.names ?? []) { 102 + const alias = value.aliases?.[name]; 103 + const spec = 104 + alias && alias !== name 105 + ? ts.factory.createExportSpecifier( 106 + false, 107 + tsc.identifier({ text: name }), 108 + tsc.identifier({ text: alias }), 109 + ) 110 + : ts.factory.createExportSpecifier( 111 + false, 112 + undefined, 113 + tsc.identifier({ text: name }), 114 + ); 115 + namedSpecifiers.push(spec); 116 + } 117 + } 118 + 119 + const node = ts.factory.createExportDeclaration( 120 + undefined, 121 + isTypeOnly, 122 + ts.factory.createNamedExports(namedSpecifiers), 123 + tsc.stringLiteral({ isSingleQuote: true, text: from }), 124 + ); 125 + statements.push(tsNodeToString({ node })); 126 + } 127 + 128 + if (statements.length) { 129 + statements.push(''); 130 + } 131 + 132 + return statements.join('\n'); 133 + } 134 + 135 + private renderImports(file: ICodegenFile, meta?: ICodegenMeta): string { 136 + const grouped = this.groupByKey(file, 'imports', meta); 137 + const statements: Array<string> = []; 138 + 139 + for (const [from, group] of grouped.entries()) { 140 + const specifiers: Array<ts.ImportSpecifier> = []; 141 + let defaultImport: ts.Identifier | undefined; 142 + let namespaceImport: ts.NamespaceImport | undefined; 143 + let isTypeOnly = false; 144 + 145 + for (const value of group) { 146 + if (value.defaultImport) { 147 + defaultImport = ts.factory.createIdentifier(value.defaultImport); 148 + if (value.typeDefaultImport) { 149 + isTypeOnly = true; 150 + } 151 + } 152 + 153 + if (typeof value.namespaceImport === 'string') { 154 + namespaceImport = ts.factory.createNamespaceImport( 155 + tsc.identifier({ text: value.namespaceImport }), 156 + ); 157 + if (value.typeNamespaceImport) { 158 + isTypeOnly = true; 159 + } 160 + } 161 + 162 + for (const name of value.names ?? []) { 163 + const alias = value.aliases?.[name]; 164 + const id = tsc.identifier({ text: name }); 165 + const spec = 166 + alias && alias !== name 167 + ? ts.factory.createImportSpecifier( 168 + false, 169 + id, 170 + tsc.identifier({ text: alias }), 171 + ) 172 + : ts.factory.createImportSpecifier(false, undefined, id); 173 + if (value.typeNames?.includes(name)) { 174 + isTypeOnly = true; 175 + } 176 + specifiers.push(spec); 177 + } 178 + } 179 + 180 + const importClause = ts.factory.createImportClause( 181 + isTypeOnly, 182 + defaultImport, 183 + namespaceImport ?? 184 + (specifiers.length 185 + ? ts.factory.createNamedImports(specifiers) 186 + : undefined), 187 + ); 188 + 189 + const node = ts.factory.createImportDeclaration( 190 + undefined, 191 + importClause, 192 + tsc.stringLiteral({ isSingleQuote: true, text: from }), 193 + ); 194 + statements.push(tsNodeToString({ node })); 195 + } 196 + 197 + if (statements.length) { 198 + statements.push(''); 199 + } 200 + 201 + return statements.join('\n'); 202 + } 203 + 204 + private renderSymbols(file: ICodegenFile): string { 205 + const results: Array<string> = []; 206 + for (const symbol of file.symbols) { 207 + if (!symbol.value) continue; 208 + if (typeof symbol.value === 'string') { 209 + results.push(symbol.value); 210 + } else { 211 + results.push(`${tsNodeToString({ node: symbol.value as any })}\n`); 212 + } 213 + } 214 + return results.join('\n'); 215 + } 216 + }
+13
packages/openapi-ts/src/ir/context.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import { CodegenProject } from '@hey-api/codegen-core'; 4 + 3 5 import type { Package } from '../config/utils/package'; 4 6 import { packageFactory } from '../config/utils/package'; 5 7 import { GeneratedFile } from '../generate/file'; 8 + import { TypeScriptRenderer } from '../generate/renderer'; 6 9 import type { PluginConfigMap } from '../plugins/config'; 7 10 import { PluginInstance } from '../plugins/shared/utils/instance'; 8 11 import type { PluginNames } from '../plugins/types'; ··· 22 25 * A map of files that will be generated from `spec`. 23 26 */ 24 27 public files: Files = {}; 28 + public gen: CodegenProject; 25 29 /** 26 30 * Intermediate representation model obtained from `spec`. 27 31 */ ··· 60 64 spec: Spec; 61 65 }) { 62 66 this.config = config; 67 + this.gen = new CodegenProject(); 63 68 this.logger = logger; 64 69 this.package = packageFactory(dependencies); 65 70 this.spec = spec; 71 + 72 + if (config.output.indexFile) { 73 + this.gen.createFile('index', { 74 + extension: '.ts', 75 + renderer: new TypeScriptRenderer(), 76 + }); 77 + } 66 78 } 67 79 68 80 /** ··· 123 135 config: plugin.config as any, 124 136 context: this as any, 125 137 dependencies: plugin.dependencies ?? [], 138 + gen: this.gen, 126 139 handler: plugin.handler, 127 140 name: plugin.name, 128 141 output: plugin.output!,
+28 -14
packages/openapi-ts/src/openApi/__tests__/index.test.ts
··· 120 120 paths: {}, 121 121 }; 122 122 parseOpenApiSpec({ 123 - // @ts-expect-error 124 - config: {}, 123 + config: { 124 + // @ts-expect-error 125 + output: {}, 126 + }, 125 127 spec, 126 128 }); 127 129 expect(parseV3_0_X).toHaveBeenCalled(); ··· 137 139 paths: {}, 138 140 }; 139 141 parseOpenApiSpec({ 140 - // @ts-expect-error 141 - config: {}, 142 + config: { 143 + // @ts-expect-error 144 + output: {}, 145 + }, 142 146 spec, 143 147 }); 144 148 expect(parseV3_0_X).toHaveBeenCalled(); ··· 154 158 paths: {}, 155 159 }; 156 160 parseOpenApiSpec({ 157 - // @ts-expect-error 158 - config: {}, 161 + config: { 162 + // @ts-expect-error 163 + output: {}, 164 + }, 159 165 spec, 160 166 }); 161 167 expect(parseV3_0_X).toHaveBeenCalled(); ··· 171 177 paths: {}, 172 178 }; 173 179 parseOpenApiSpec({ 174 - // @ts-expect-error 175 - config: {}, 180 + config: { 181 + // @ts-expect-error 182 + output: {}, 183 + }, 176 184 spec, 177 185 }); 178 186 expect(parseV3_0_X).toHaveBeenCalled(); ··· 188 196 paths: {}, 189 197 }; 190 198 parseOpenApiSpec({ 191 - // @ts-expect-error 192 - config: {}, 199 + config: { 200 + // @ts-expect-error 201 + output: {}, 202 + }, 193 203 spec, 194 204 }); 195 205 expect(parseV3_0_X).toHaveBeenCalled(); ··· 204 214 openapi: '3.1.0', 205 215 }; 206 216 parseOpenApiSpec({ 207 - // @ts-expect-error 208 - config: {}, 217 + config: { 218 + // @ts-expect-error 219 + output: {}, 220 + }, 209 221 spec, 210 222 }); 211 223 expect(parseV3_1_X).toHaveBeenCalled(); ··· 220 232 openapi: '3.1.1', 221 233 }; 222 234 parseOpenApiSpec({ 223 - // @ts-expect-error 224 - config: {}, 235 + config: { 236 + // @ts-expect-error 237 + output: {}, 238 + }, 225 239 spec, 226 240 }); 227 241 expect(parseV3_1_X).toHaveBeenCalled();
+9
packages/openapi-ts/src/overrides.d.ts
··· 1 + import '@hey-api/codegen-core'; 2 + 3 + import type ts from 'typescript'; 4 + 5 + declare module '@hey-api/codegen-core' { 6 + interface ICodegenMeta { 7 + moduleResolution?: ts.ModuleResolutionKind; 8 + } 9 + }
+3
packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.test.ts
··· 1 1 import fs from 'node:fs'; 2 2 3 + import { CodegenProject } from '@hey-api/codegen-core'; 3 4 import type ts from 'typescript'; 4 5 import { describe, expect, it, vi } from 'vitest'; 5 6 ··· 134 135 }, 135 136 context: {} as any, 136 137 dependencies: [], 138 + gen: new CodegenProject(), 137 139 handler: () => {}, 138 140 name: '@hey-api/schemas', 139 141 output: 'schemas', ··· 274 276 }, 275 277 context: {} as any, 276 278 dependencies: [], 279 + gen: new CodegenProject(), 277 280 handler: () => {}, 278 281 name: '@hey-api/schemas', 279 282 output: 'schemas',
+5
packages/openapi-ts/src/plugins/@hey-api/sdk/__tests__/plugin.test.ts
··· 1 1 import fs from 'node:fs'; 2 2 import path from 'node:path'; 3 3 4 + import { CodegenProject } from '@hey-api/codegen-core'; 4 5 import type ts from 'typescript'; 5 6 import { describe, expect, it, vi } from 'vitest'; 6 7 ··· 170 171 }, 171 172 context: {} as any, 172 173 dependencies: [], 174 + gen: new CodegenProject(), 173 175 handler: () => {}, 174 176 name: '@hey-api/sdk', 175 177 output: '', ··· 342 344 }, 343 345 context: {} as any, 344 346 dependencies: [], 347 + gen: new CodegenProject(), 345 348 handler: () => {}, 346 349 name: '@hey-api/sdk', 347 350 output: '', ··· 475 478 }, 476 479 context: {} as any, 477 480 dependencies: [], 481 + gen: new CodegenProject(), 478 482 handler: () => {}, 479 483 name: '@hey-api/sdk', 480 484 output: '', ··· 610 614 }, 611 615 context: {} as any, 612 616 dependencies: [], 617 + gen: new CodegenProject(), 613 618 handler: () => {}, 614 619 name: '@hey-api/sdk', 615 620 output: '',
+40 -2
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 1 1 import ts from 'typescript'; 2 2 3 + import type { 4 + EnsureUniqueIdentifierData, 5 + GeneratedFile, 6 + } from '../../../generate/file'; 7 + import { parseRef } from '../../../generate/file'; 8 + import type { Identifier, Namespace } from '../../../generate/file/types'; 3 9 import { 4 10 createOperationKey, 5 11 operationResponsesMap, ··· 111 117 return nodes; 112 118 }; 113 119 120 + /** 121 + * Prevents a specific identifier from being created. This is useful for 122 + * transformers where we know a certain transformer won't be needed, and 123 + * we want to avoid attempting to create since we know it won't happen. 124 + */ 125 + const blockIdentifier = ({ 126 + $ref, 127 + file, 128 + namespace, 129 + }: Pick<EnsureUniqueIdentifierData, '$ref'> & { 130 + file: GeneratedFile; 131 + namespace: Namespace; 132 + }): Identifier => { 133 + const { name, ref } = parseRef($ref); 134 + const refValue = 135 + file.identifiers[name.toLocaleLowerCase()]?.[namespace]?.[ref]; 136 + if (!refValue) { 137 + throw new Error( 138 + `Identifier for $ref ${$ref} in namespace ${namespace} not found`, 139 + ); 140 + } 141 + 142 + refValue.name = false; 143 + 144 + return { 145 + created: false, 146 + name: refValue.name, 147 + }; 148 + }; 149 + 114 150 const processSchemaType = ({ 115 151 dataExpression, 116 152 plugin, ··· 158 194 } else { 159 195 // the created schema response transformer was empty, do not generate 160 196 // it and prevent any future attempts 161 - identifier = file.blockIdentifier({ 197 + identifier = blockIdentifier({ 162 198 $ref: schemaResponseTransformerRef({ $ref: schema.$ref }), 199 + file, 163 200 namespace: 'value', 164 201 }); 165 202 } ··· 474 511 } else { 475 512 // the created schema response transformer was empty, do not generate 476 513 // it and prevent any future attempts 477 - identifierResponseTransformer = file.blockIdentifier({ 514 + identifierResponseTransformer = blockIdentifier({ 478 515 $ref: operationTransformerIrRef({ 479 516 id: operation.id, 480 517 type: 'response', 481 518 }), 519 + file, 482 520 namespace: 'value', 483 521 }); 484 522 }
+2
packages/openapi-ts/src/plugins/@hey-api/typescript/__tests__/plugin.test.ts
··· 1 1 import fs from 'node:fs'; 2 2 3 + import { CodegenProject } from '@hey-api/codegen-core'; 3 4 import type ts from 'typescript'; 4 5 import { describe, expect, it, vi } from 'vitest'; 5 6 ··· 163 164 }, 164 165 context: {} as any, 165 166 dependencies: [], 167 + gen: new CodegenProject(), 166 168 handler: () => {}, 167 169 name: '@hey-api/typescript', 168 170 output: '',
+5
packages/openapi-ts/src/plugins/shared/utils/instance.ts
··· 1 + import type { CodegenProject } from '@hey-api/codegen-core'; 2 + 1 3 import { HeyApiError } from '../../../error'; 2 4 import type { IR } from '../../../ir/types'; 3 5 import type { OpenApi } from '../../../openApi/types'; ··· 26 28 config: Omit<T['resolvedConfig'], 'name' | 'output'>; 27 29 context: IR.Context; 28 30 dependencies: Required<Plugin.Config<T>>['dependencies'] = []; 31 + gen: CodegenProject; 29 32 private handler: Plugin.Config<T>['handler']; 30 33 name: T['resolvedConfig']['name']; 31 34 output: Required<T['config']>['output']; ··· 44 47 > & { 45 48 api?: T['api']; 46 49 context: IR.Context<OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X>; 50 + gen: CodegenProject; 47 51 name: string; 48 52 output: string; 49 53 }, ··· 52 56 this.config = props.config; 53 57 this.context = props.context; 54 58 this.dependencies = props.dependencies; 59 + this.gen = props.gen; 55 60 this.handler = props.handler; 56 61 this.name = props.name; 57 62 this.output = props.output;
+12
packages/openapi-ts/src/plugins/valibot/api.ts
··· 16 16 plugin: ValibotPlugin['Instance']; 17 17 }): ts.ArrowFunction | undefined => { 18 18 const { requests } = plugin.config; 19 + // const f = plugin.gen.ensureFile(plugin.output); 20 + // TODO: replace 19 21 const schemaIdentifier = plugin.context.file({ id: valibotId })!.identifier({ 20 22 // TODO: refactor for better cross-plugin compatibility 21 23 $ref: `#/valibot-response/${operation.id}`, ··· 35 37 }), 36 38 name: schemaIdentifier.name, 37 39 }); 40 + // file.import({ 41 + // module: f.relativePathFromFile({ path: file.nameWithoutExtension() }), 42 + // name: schemaIdentifier.name, 43 + // }); 38 44 39 45 file.import({ 40 46 alias: identifiers.v.text, ··· 80 86 plugin: ValibotPlugin['Instance']; 81 87 }): ts.ArrowFunction | undefined => { 82 88 const { responses } = plugin.config; 89 + // const f = plugin.gen.ensureFile(plugin.output); 90 + // TODO: replace 83 91 const schemaIdentifier = plugin.context.file({ id: valibotId })!.identifier({ 84 92 // TODO: refactor for better cross-plugin compatibility 85 93 $ref: `#/valibot-response/${operation.id}`, ··· 99 107 }), 100 108 name: schemaIdentifier.name, 101 109 }); 110 + // file.import({ 111 + // module: f.relativePathFromFile({ path: file.nameWithoutExtension() }), 112 + // name: schemaIdentifier.name, 113 + // }); 102 114 103 115 file.import({ 104 116 alias: identifiers.v.text,
+21
packages/openapi-ts/src/plugins/valibot/plugin.ts
··· 1 1 import ts from 'typescript'; 2 2 3 3 import type { Identifier } from '../../generate/file/types'; 4 + // import { TypeScriptRenderer } from '../../generate/renderer'; 4 5 import { deduplicateSchema } from '../../ir/schema'; 5 6 import type { IR } from '../../ir/types'; 6 7 import { tsc } from '../../tsc'; ··· 954 955 schema: IR.SchemaObject; 955 956 state: State; 956 957 }): Array<ts.Expression> => { 958 + // TODO: replace 957 959 const file = plugin.context.file({ id: valibotId })!; 960 + // const f = plugin.gen.ensureFile(plugin.output); 958 961 959 962 let anyType: string | undefined; 960 963 let identifier: ReturnType<typeof file.identifier> | undefined = _identifier; ··· 971 974 nameTransformer: state.nameTransformer, 972 975 namespace: 'value', 973 976 }); 977 + // TODO: claim unique name 978 + // f.addSymbol({ name: '' }); 974 979 } 975 980 } 976 981 ··· 1180 1185 : undefined, 1181 1186 }); 1182 1187 file.add(statement); 1188 + // TODO: update claimed name 1189 + // f.addSymbol({ 1190 + // name: identifier.name, 1191 + // value: statement, 1192 + // }); 1183 1193 1184 1194 return []; 1185 1195 } ··· 1193 1203 id: valibotId, 1194 1204 path: plugin.output, 1195 1205 }); 1206 + // const f = plugin.gen.createFile(plugin.output, { 1207 + // extension: '.ts', 1208 + // path: '{{path}}.gen', 1209 + // renderer: new TypeScriptRenderer(), 1210 + // }); 1196 1211 1197 1212 file.import({ 1198 1213 alias: identifiers.v.text, 1199 1214 module: 'valibot', 1200 1215 name: '*', 1201 1216 }); 1217 + // f.addImport({ from: 'valibot', namespaceImport: identifiers.v.text }); 1202 1218 1203 1219 plugin.forEach( 1204 1220 'operation', ··· 1250 1266 } 1251 1267 }, 1252 1268 ); 1269 + 1270 + // if (plugin.config.exportFromIndex && f.hasContent()) { 1271 + // const index = plugin.gen.ensureFile('index'); 1272 + // index.addExport({ from: f, namespaceImport: true }); 1273 + // } 1253 1274 };
+62 -44
packages/openapi-ts/src/plugins/zod/v4/plugin.ts
··· 1 1 import ts from 'typescript'; 2 2 3 + // import { TypeScriptRenderer } from '../../../generate/renderer'; 3 4 import { deduplicateSchema } from '../../../ir/schema'; 4 5 import type { IR } from '../../../ir/types'; 5 6 import { buildName } from '../../../openApi/shared/utils/name'; ··· 1100 1101 id: zodId, 1101 1102 path: plugin.output, 1102 1103 }); 1104 + // const f = plugin.gen.createFile(plugin.output, { 1105 + // extension: '.ts', 1106 + // path: '{{path}}.gen', 1107 + // renderer: new TypeScriptRenderer(), 1108 + // }); 1103 1109 1104 1110 file.import({ 1105 1111 module: getZodModule({ plugin }), 1106 1112 name: identifiers.z.text, 1107 1113 }); 1114 + // f.addImport({ from: getZodModule({ plugin }), names: [identifiers.z.text] }); 1108 1115 1109 1116 plugin.forEach( 1110 1117 'operation', ··· 1113 1120 'schema', 1114 1121 'webhook', 1115 1122 (event) => { 1116 - if (event.type === 'operation') { 1117 - operationToZodSchema({ 1118 - getZodSchema: (schema) => { 1119 - const state: State = { 1120 - circularReferenceTracker: [], 1121 - currentReferenceTracker: [], 1122 - hasCircularReference: false, 1123 - }; 1124 - return schemaToZodSchema({ plugin, schema, state }); 1125 - }, 1126 - operation: event.operation, 1127 - plugin, 1128 - }); 1129 - } else if (event.type === 'parameter') { 1130 - handleComponent({ 1131 - id: event.$ref, 1132 - plugin, 1133 - schema: event.parameter.schema, 1134 - }); 1135 - } else if (event.type === 'requestBody') { 1136 - handleComponent({ 1137 - id: event.$ref, 1138 - plugin, 1139 - schema: event.requestBody.schema, 1140 - }); 1141 - } else if (event.type === 'schema') { 1142 - handleComponent({ 1143 - id: event.$ref, 1144 - plugin, 1145 - schema: event.schema, 1146 - }); 1147 - } else if (event.type === 'webhook') { 1148 - webhookToZodSchema({ 1149 - getZodSchema: (schema) => { 1150 - const state: State = { 1151 - circularReferenceTracker: [], 1152 - currentReferenceTracker: [], 1153 - hasCircularReference: false, 1154 - }; 1155 - return schemaToZodSchema({ plugin, schema, state }); 1156 - }, 1157 - operation: event.operation, 1158 - plugin, 1159 - }); 1123 + switch (event.type) { 1124 + case 'operation': 1125 + operationToZodSchema({ 1126 + getZodSchema: (schema) => { 1127 + const state: State = { 1128 + circularReferenceTracker: [], 1129 + currentReferenceTracker: [], 1130 + hasCircularReference: false, 1131 + }; 1132 + return schemaToZodSchema({ plugin, schema, state }); 1133 + }, 1134 + operation: event.operation, 1135 + plugin, 1136 + }); 1137 + break; 1138 + case 'parameter': 1139 + handleComponent({ 1140 + id: event.$ref, 1141 + plugin, 1142 + schema: event.parameter.schema, 1143 + }); 1144 + break; 1145 + case 'requestBody': 1146 + handleComponent({ 1147 + id: event.$ref, 1148 + plugin, 1149 + schema: event.requestBody.schema, 1150 + }); 1151 + break; 1152 + case 'schema': 1153 + handleComponent({ 1154 + id: event.$ref, 1155 + plugin, 1156 + schema: event.schema, 1157 + }); 1158 + break; 1159 + case 'webhook': 1160 + webhookToZodSchema({ 1161 + getZodSchema: (schema) => { 1162 + const state: State = { 1163 + circularReferenceTracker: [], 1164 + currentReferenceTracker: [], 1165 + hasCircularReference: false, 1166 + }; 1167 + return schemaToZodSchema({ plugin, schema, state }); 1168 + }, 1169 + operation: event.operation, 1170 + plugin, 1171 + }); 1172 + break; 1160 1173 } 1161 1174 }, 1162 1175 ); 1176 + 1177 + // if (plugin.config.exportFromIndex && f.hasContent()) { 1178 + // const index = plugin.gen.ensureFile('index'); 1179 + // index.addExport({ from: f, namespaceImport: true }); 1180 + // } 1163 1181 };
+3 -1
packages/openapi-ts/src/tsc/module.ts
··· 17 17 */ 18 18 export const createExportAllDeclaration = ({ 19 19 module, 20 + shouldAppendJs, 20 21 }: { 21 22 module: string; 23 + shouldAppendJs?: boolean; 22 24 }): ts.ExportDeclaration => { 23 25 const statement = ts.factory.createExportDeclaration( 24 26 undefined, 25 27 false, 26 28 undefined, 27 - ots.string(module), 29 + ots.string(shouldAppendJs ? `${module}.js` : module), 28 30 ); 29 31 return statement; 30 32 };
+1 -1
packages/openapi-ts/turbo.json
··· 3 3 "extends": ["//"], 4 4 "tasks": { 5 5 "build": { 6 - "dependsOn": [], 6 + "dependsOn": ["^build"], 7 7 "outputs": ["dist/**"] 8 8 } 9 9 }
+29 -238
pnpm-lock.yaml
··· 161 161 devDependencies: 162 162 '@angular-devkit/build-angular': 163 163 specifier: 19.2.0 164 - version: 19.2.0(696c3532ef15b073c21785e1bc79a040) 164 + version: 19.2.0(b57b04a4dfd0d7238fcb437f41884422) 165 165 '@angular/cli': 166 166 specifier: 19.2.0 167 167 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) ··· 255 255 devDependencies: 256 256 '@angular-devkit/build-angular': 257 257 specifier: 19.2.0 258 - version: 19.2.0(b57b04a4dfd0d7238fcb437f41884422) 258 + version: 19.2.0(696c3532ef15b073c21785e1bc79a040) 259 259 '@angular/cli': 260 260 specifier: 19.2.0 261 261 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) ··· 1085 1085 specifier: 2.2.0 1086 1086 version: 2.2.0(typescript@5.8.3) 1087 1087 1088 + packages/codegen-core: 1089 + devDependencies: 1090 + '@config/vite-base': 1091 + specifier: workspace:* 1092 + version: link:../config-vite-base 1093 + eslint: 1094 + specifier: 9.17.0 1095 + version: 9.17.0(jiti@2.5.1) 1096 + prettier: 1097 + specifier: 3.4.2 1098 + version: 3.4.2 1099 + typescript: 1100 + specifier: 5.8.3 1101 + version: 5.8.3 1102 + 1088 1103 packages/config-vite-base: 1089 1104 dependencies: 1090 1105 vite: ··· 1146 1161 1147 1162 packages/openapi-ts: 1148 1163 dependencies: 1164 + '@hey-api/codegen-core': 1165 + specifier: workspace:^0.0.0 1166 + version: link:../codegen-core 1149 1167 '@hey-api/json-schema-ref-parser': 1150 1168 specifier: 1.0.7 1151 1169 version: 1.0.7 ··· 1233 1251 version: 3.3.2 1234 1252 nuxt: 1235 1253 specifier: 3.14.1592 1236 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.49.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 1254 + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.49.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 1237 1255 prettier: 1238 1256 specifier: 3.4.2 1239 1257 version: 3.4.2 ··· 17620 17638 17621 17639 '@nuxt/devalue@2.0.2': {} 17622 17640 17623 - '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))': 17624 - dependencies: 17625 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 17626 - '@nuxt/schema': 3.16.2 17627 - execa: 7.2.0 17628 - vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 17629 - transitivePeerDependencies: 17630 - - magicast 17631 - - supports-color 17632 - 17633 17641 '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 17634 17642 dependencies: 17635 17643 '@nuxt/kit': 3.15.4(magicast@0.3.5) ··· 17691 17699 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 17692 17700 vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 17693 17701 vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 17694 - which: 3.0.1 17695 - ws: 8.18.3 17696 - transitivePeerDependencies: 17697 - - bufferutil 17698 - - rollup 17699 - - supports-color 17700 - - utf-8-validate 17701 - - vue 17702 - 17703 - '@nuxt/devtools@1.7.0(rollup@4.49.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': 17704 - dependencies: 17705 - '@antfu/utils': 0.7.10 17706 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 17707 - '@nuxt/devtools-wizard': 1.7.0 17708 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 17709 - '@vue/devtools-core': 7.6.8(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) 17710 - '@vue/devtools-kit': 7.6.8 17711 - birpc: 0.2.19 17712 - consola: 3.4.2 17713 - cronstrue: 2.59.0 17714 - destr: 2.0.5 17715 - error-stack-parser-es: 0.1.5 17716 - execa: 7.2.0 17717 - fast-npm-meta: 0.2.2 17718 - flatted: 3.3.3 17719 - get-port-please: 3.2.0 17720 - hookable: 5.5.3 17721 - image-meta: 0.2.1 17722 - is-installed-globally: 1.0.0 17723 - launch-editor: 2.11.1 17724 - local-pkg: 0.5.1 17725 - magicast: 0.3.5 17726 - nypm: 0.4.1 17727 - ohash: 1.1.6 17728 - pathe: 1.1.2 17729 - perfect-debounce: 1.0.0 17730 - pkg-types: 1.3.1 17731 - rc9: 2.1.2 17732 - scule: 1.3.0 17733 - semver: 7.7.2 17734 - simple-git: 3.28.0 17735 - sirv: 3.0.1 17736 - tinyglobby: 0.2.10 17737 - unimport: 3.14.6(rollup@4.49.0) 17738 - vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 17739 - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.49.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 17740 - vite-plugin-vue-inspector: 5.3.2(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 17741 17702 which: 3.0.1 17742 17703 ws: 8.18.3 17743 17704 transitivePeerDependencies: ··· 20412 20373 dependencies: 20413 20374 '@vue/devtools-kit': 8.0.1 20414 20375 20415 - '@vue/devtools-core@7.6.8(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': 20416 - dependencies: 20417 - '@vue/devtools-kit': 7.6.8 20418 - '@vue/devtools-shared': 7.7.7 20419 - mitt: 3.0.1 20420 - nanoid: 5.1.5 20421 - pathe: 1.1.2 20422 - vite-hot-client: 0.2.4(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 20423 - vue: 3.5.13(typescript@5.8.3) 20424 - transitivePeerDependencies: 20425 - - vite 20426 - 20427 20376 '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 20428 20377 dependencies: 20429 20378 '@vue/devtools-kit': 7.6.8 ··· 22502 22451 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3) 22503 22452 eslint: 9.17.0(jiti@2.5.1) 22504 22453 eslint-import-resolver-node: 0.3.9 22505 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)) 22506 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22454 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22455 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22507 22456 eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.5.1)) 22508 22457 eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.5.1)) 22509 22458 eslint-plugin-react-hooks: 5.2.0(eslint@9.17.0(jiti@2.5.1)) ··· 22526 22475 transitivePeerDependencies: 22527 22476 - supports-color 22528 22477 22529 - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)): 22478 + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 22530 22479 dependencies: 22531 22480 '@nolyfill/is-core-module': 1.0.39 22532 22481 debug: 4.4.1 ··· 22537 22486 tinyglobby: 0.2.14 22538 22487 unrs-resolver: 1.11.1 22539 22488 optionalDependencies: 22540 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22489 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22541 22490 transitivePeerDependencies: 22542 22491 - supports-color 22543 22492 22544 - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)): 22493 + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 22545 22494 dependencies: 22546 22495 debug: 3.2.7 22547 22496 optionalDependencies: 22548 22497 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3) 22549 22498 eslint: 9.17.0(jiti@2.5.1) 22550 22499 eslint-import-resolver-node: 0.3.9 22551 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)) 22500 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22552 22501 transitivePeerDependencies: 22553 22502 - supports-color 22554 22503 22555 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)): 22504 + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 22556 22505 dependencies: 22557 22506 '@rtsao/scc': 1.1.0 22558 22507 array-includes: 3.1.9 ··· 22563 22512 doctrine: 2.1.0 22564 22513 eslint: 9.17.0(jiti@2.5.1) 22565 22514 eslint-import-resolver-node: 0.3.9 22566 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22515 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22567 22516 hasown: 2.0.2 22568 22517 is-core-module: 2.16.1 22569 22518 is-glob: 4.0.3 ··· 25453 25402 - vue-tsc 25454 25403 - xml2js 25455 25404 25456 - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.49.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 25457 - dependencies: 25458 - '@nuxt/devalue': 2.0.2 25459 - '@nuxt/devtools': 1.7.0(rollup@4.49.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) 25460 - '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.49.0) 25461 - '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.49.0) 25462 - '@nuxt/telemetry': 2.6.6(magicast@0.3.5) 25463 - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.49.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) 25464 - '@unhead/dom': 1.11.20 25465 - '@unhead/shared': 1.11.20 25466 - '@unhead/ssr': 1.11.20 25467 - '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.3)) 25468 - '@vue/shared': 3.5.20 25469 - acorn: 8.14.0 25470 - c12: 2.0.1(magicast@0.3.5) 25471 - chokidar: 4.0.3 25472 - compatx: 0.1.8 25473 - consola: 3.4.2 25474 - cookie-es: 1.2.2 25475 - defu: 6.1.4 25476 - destr: 2.0.5 25477 - devalue: 5.3.2 25478 - errx: 0.1.0 25479 - esbuild: 0.24.2 25480 - escape-string-regexp: 5.0.0 25481 - estree-walker: 3.0.3 25482 - globby: 14.1.0 25483 - h3: 1.15.4 25484 - hookable: 5.5.3 25485 - ignore: 6.0.2 25486 - impound: 0.2.2(rollup@4.49.0) 25487 - jiti: 2.5.1 25488 - klona: 2.0.6 25489 - knitwork: 1.2.0 25490 - magic-string: 0.30.18 25491 - mlly: 1.7.4 25492 - nanotar: 0.1.1 25493 - nitropack: 2.12.4(@netlify/blobs@9.1.2)(encoding@0.1.13) 25494 - nuxi: 3.28.0 25495 - nypm: 0.3.12 25496 - ofetch: 1.4.1 25497 - ohash: 1.1.6 25498 - pathe: 1.1.2 25499 - perfect-debounce: 1.0.0 25500 - pkg-types: 1.3.1 25501 - radix3: 1.1.2 25502 - scule: 1.3.0 25503 - semver: 7.7.2 25504 - std-env: 3.9.0 25505 - strip-literal: 2.1.1 25506 - tinyglobby: 0.2.10 25507 - ufo: 1.6.1 25508 - ultrahtml: 1.6.0 25509 - uncrypto: 0.1.3 25510 - unctx: 2.4.1 25511 - unenv: 1.10.0 25512 - unhead: 1.11.20 25513 - unimport: 3.14.6(rollup@4.49.0) 25514 - unplugin: 1.16.1 25515 - unplugin-vue-router: 0.10.9(rollup@4.49.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) 25516 - unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) 25517 - untyped: 1.5.2 25518 - vue: 3.5.13(typescript@5.8.3) 25519 - vue-bundle-renderer: 2.1.2 25520 - vue-devtools-stub: 0.1.0 25521 - vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) 25522 - optionalDependencies: 25523 - '@parcel/watcher': 2.5.1 25524 - '@types/node': 22.10.5 25525 - transitivePeerDependencies: 25526 - - '@azure/app-configuration' 25527 - - '@azure/cosmos' 25528 - - '@azure/data-tables' 25529 - - '@azure/identity' 25530 - - '@azure/keyvault-secrets' 25531 - - '@azure/storage-blob' 25532 - - '@biomejs/biome' 25533 - - '@capacitor/preferences' 25534 - - '@deno/kv' 25535 - - '@electric-sql/pglite' 25536 - - '@libsql/client' 25537 - - '@netlify/blobs' 25538 - - '@planetscale/database' 25539 - - '@upstash/redis' 25540 - - '@vercel/blob' 25541 - - '@vercel/functions' 25542 - - '@vercel/kv' 25543 - - aws4fetch 25544 - - better-sqlite3 25545 - - bufferutil 25546 - - db0 25547 - - drizzle-orm 25548 - - encoding 25549 - - eslint 25550 - - idb-keyval 25551 - - ioredis 25552 - - less 25553 - - lightningcss 25554 - - magicast 25555 - - meow 25556 - - mysql2 25557 - - optionator 25558 - - rolldown 25559 - - rollup 25560 - - sass 25561 - - sass-embedded 25562 - - sqlite3 25563 - - stylelint 25564 - - stylus 25565 - - sugarss 25566 - - supports-color 25567 - - terser 25568 - - typescript 25569 - - uploadthing 25570 - - utf-8-validate 25571 - - vite 25572 - - vls 25573 - - vti 25574 - - vue-tsc 25575 - - xml2js 25576 - 25577 25405 nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.49.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 25578 25406 dependencies: 25579 25407 '@nuxt/devalue': 2.0.2 ··· 28738 28566 '@types/unist': 3.0.3 28739 28567 vfile-message: 4.0.3 28740 28568 28741 - vite-hot-client@0.2.4(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 28742 - dependencies: 28743 - vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 28744 - 28745 28569 vite-hot-client@0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 28746 28570 dependencies: 28747 28571 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) ··· 28872 28696 - rollup 28873 28697 - supports-color 28874 28698 28875 - vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.49.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 28876 - dependencies: 28877 - '@antfu/utils': 0.7.10 28878 - '@rollup/pluginutils': 5.2.0(rollup@4.49.0) 28879 - debug: 4.4.1 28880 - error-stack-parser-es: 0.1.5 28881 - fs-extra: 11.3.1 28882 - open: 10.1.2 28883 - perfect-debounce: 1.0.0 28884 - picocolors: 1.1.1 28885 - sirv: 3.0.1 28886 - vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 28887 - optionalDependencies: 28888 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 28889 - transitivePeerDependencies: 28890 - - rollup 28891 - - supports-color 28892 - 28893 28699 vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.49.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 28894 28700 dependencies: 28895 28701 '@antfu/utils': 0.7.10 ··· 28923 28729 - rollup 28924 28730 - supports-color 28925 28731 - vue 28926 - 28927 - vite-plugin-vue-inspector@5.3.2(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 28928 - dependencies: 28929 - '@babel/core': 7.28.3 28930 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) 28931 - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) 28932 - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) 28933 - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) 28934 - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) 28935 - '@vue/compiler-dom': 3.5.20 28936 - kolorist: 1.8.0 28937 - magic-string: 0.30.18 28938 - vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 28939 - transitivePeerDependencies: 28940 - - supports-color 28941 28732 28942 28733 vite-plugin-vue-inspector@5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 28943 28734 dependencies: