diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 59be383..a3b8a6b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# @grafikart/o2ts
-A simple tool to convert OpenAPI 3.0/3.1 specs into a TypeScript file with useful types.
+Un outil simple et puissant pour convertir des spécifications OpenAPI 3.0/3.1 en fichiers TypeScript avec des types utilisables.
-## Install
+## Installation
```bash
npm i -D @grafikart/o2ts
@@ -10,9 +10,9 @@ yarn add -D @grafikart/o2ts
pnpm add -D @grafikart/o2ts
```
-## Usage
+## Utilisation basique
-Add the following script to your `package.json`:
+Ajoutez le script suivant à votre `package.json` :
```json
{
@@ -22,26 +22,213 @@ Add the following script to your `package.json`:
}
```
-Then:
+Puis exécutez :
```bash
npm run openapi
```
-## Example
+Par défaut, `o2ts` générera un fichier TypeScript avec le même nom que votre fichier OpenAPI (en remplaçant l'extension `.yml` ou `.yaml` par `.ts`).
-Here is an example of using the definitions generated with the tool:
+## Utilisation avancée
-```ts
+### Spécifier un nom de fichier de sortie
+
+```bash
+o2ts ./openapi.yml ./types/api.ts
+```
+
+### Utiliser avec différentes sources OpenAPI
+
+Le package peut traiter les spécifications OpenAPI depuis :
+
+- Un fichier local : `o2ts ./path/to/openapi.yml`
+- Un URL (bientôt disponible) : `o2ts https://example.com/api/openapi.json`
+
+## Configuration
+
+@grafikart/o2ts est configurable pour s'adapter à vos besoins. Vous pouvez configurer l'outil de deux façons :
+
+### 1. Fichier `.o2tsrc.json`
+
+Créez un fichier `.o2tsrc.json` à la racine de votre projet :
+
+```json
+{
+ "format": {
+ "semi": true,
+ "tabWidth": 4,
+ "singleQuote": false,
+ "trailingComma": false
+ },
+ "generator": {
+ "typePrefix": "MyAPI",
+ "includeExamples": true
+ }
+}
+```
+
+### 2. Section dans `package.json`
+
+Ou ajoutez une section `o2ts` dans votre `package.json` :
+
+```json
+{
+ "name": "votre-projet",
+ "version": "1.0.0",
+ "o2ts": {
+ "format": {
+ "semi": true,
+ "singleQuote": false
+ },
+ "generator": {
+ "typePrefix": "MyAPI"
+ }
+ }
+}
+```
+
+### Options de configuration disponibles
+
+#### Format du code généré
+
+| Option | Type | Défaut | Description |
+|--------|------|--------|-------------|
+| `format.semi` | `boolean` | `false` | Ajouter des points-virgules à la fin des lignes |
+| `format.tabWidth` | `number` | `2` | Largeur d'indentation |
+| `format.singleQuote` | `boolean` | `true` | Utiliser des guillemets simples au lieu de doubles |
+| `format.trailingComma` | `boolean` | `true` | Ajouter des virgules finales |
+
+#### Options du générateur
+
+| Option | Type | Défaut | Description |
+|--------|------|--------|-------------|
+| `generator.typePrefix` | `string` | `"API"` | Préfixe pour les types générés |
+| `generator.includeExamples` | `boolean` | `false` | Inclure les exemples de requête dans les commentaires |
+| `generator.includeRequestBodies` | `boolean` | `true` | Générer des types pour les corps de requête |
+| `generator.includeResponses` | `boolean` | `true` | Générer des types pour les réponses |
+
+## Exemples d'utilisation
+
+### Exemple basique
+
+Voici un exemple simple d'utilisation des types générés :
+
+```typescript
import type { APIPaths, APIRequests, APIResponse } from './openapi'
export async function fetchAPI<
Path extends APIPaths,
Options extends APIRequests
-> (path: Path, options: Options): Promise> {
- // Your code here
+>(path: Path, options: Options): Promise> {
+ // Votre code ici
}
```
-You can find more implementations in the [examples directory](./examples).
+### Exemple avec Axios
+
+```typescript
+import axios from 'axios'
+import type { APIPaths, APIRequest, APIResponse } from './openapi'
+
+export async function apiCall<
+ Path extends APIPaths,
+ Method extends string | undefined = undefined
+>(
+ path: Path,
+ options?: APIRequest
+): Promise> {
+ const method = (options?.method ?? 'get').toLowerCase()
+ const config = {
+ params: options?.query,
+ data: options?.body
+ }
+
+ const response = await axios.request({
+ url: path,
+ method,
+ ...config
+ })
+
+ return response.data
+}
+
+// Utilisation
+const users = await apiCall('/users')
+const user = await apiCall('/users/{id}', {
+ method: 'get',
+ urlParams: { id: 123 }
+})
+const newUser = await apiCall('/users', {
+ method: 'post',
+ body: { name: 'John' }
+})
+```
+
+### Exemple avec React Query
+
+```typescript
+import { useQuery } from 'react-query'
+import type { APIPaths, APIRequest, APIResponse } from './openapi'
+
+// Fonction API de base
+const apiCall = async<
+ Path extends APIPaths,
+ Method extends string | undefined = undefined
+>(
+ path: Path,
+ options?: APIRequest
+): Promise> => {
+ // Implémentation de l'appel API
+}
+
+// Hook personnalisé pour React Query
+export function useApiQuery<
+ Path extends APIPaths,
+ Method extends 'get' | undefined = 'get'
+>(
+ path: Path,
+ options?: APIRequest,
+ queryOptions?: any
+) {
+ return useQuery(
+ [path, options],
+ () => apiCall(path, { method: 'get', ...options } as any),
+ queryOptions
+ )
+}
+
+// Utilisation
+function UserComponent({ userId }: { userId: number }) {
+ const { data, isLoading } = useApiQuery('/users/{id}', {
+ urlParams: { id: userId }
+ })
+
+ if (isLoading) return Loading...
+
+ return {data.name}
+}
+```
+
+## Types générés
+
+L'outil génère les types suivants :
+
+- `APIPaths`: Union des chemins d'API disponibles
+- `APIRequests`: Type pour les options de requête pour un chemin donné
+- `APIMethods`: Union des méthodes HTTP disponibles pour un chemin donné
+- `APIRequest`: Type pour une requête complète avec une méthode spécifique
+- `APIResponse`: Type pour la réponse d'une requête avec une méthode spécifique
+
+D'autres types générés incluent :
+- `APISchemas`: Types pour tous les schémas définis dans les `components.schemas`
+- `APIParameters`: Types pour tous les paramètres définis dans les `components.parameters`
+- `APIResponses`: Types pour toutes les réponses définies dans les `components.responses`
+
+## Contribuer
+
+Les contributions sont les bienvenues ! N'hésitez pas à ouvrir une issue ou une pull request sur [le dépôt GitHub](https://github.com/Grafikart/OpenApiToTS).
+
+## Licence
+ISC
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..15d6713
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,345 @@
+{
+ "name": "@grafikart/o2ts",
+ "version": "0.1.15",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@grafikart/o2ts",
+ "version": "0.1.15",
+ "license": "ISC",
+ "dependencies": {
+ "@readme/openapi-parser": "^2.5.0",
+ "prettier": "^3.2.5"
+ },
+ "bin": {
+ "o2ts": "dist/generator.js"
+ },
+ "devDependencies": {
+ "@types/bun": "^1.0.12",
+ "@types/node": "^20.12.3",
+ "openapi-types": "^12.1.3",
+ "typescript": "^5.4.3"
+ }
+ },
+ "node_modules/@apidevtools/swagger-methods": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
+ "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
+ "license": "MIT"
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@humanwhocodes/momoa": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz",
+ "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@jsdevtools/ono": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
+ "license": "MIT"
+ },
+ "node_modules/@readme/better-ajv-errors": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@readme/better-ajv-errors/-/better-ajv-errors-2.3.2.tgz",
+ "integrity": "sha512-T4GGnRAlY3C339NhoUpgJJFsMYko9vIgFAlhgV+/vEGFw66qEY4a4TRJIAZBcX/qT1pq5DvXSme+SQODHOoBrw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/code-frame": "^7.22.5",
+ "@babel/runtime": "^7.22.5",
+ "@humanwhocodes/momoa": "^2.0.3",
+ "jsonpointer": "^5.0.0",
+ "leven": "^3.1.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "ajv": "4.11.8 - 8"
+ }
+ },
+ "node_modules/@readme/json-schema-ref-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@readme/json-schema-ref-parser/-/json-schema-ref-parser-1.2.1.tgz",
+ "integrity": "sha512-FKCnFnpKklBPu8atyXqmSRBPSYlZLdcdbIilX19y0vVFiVthqKV9SQp4GZ8L4rOqSVmjn14uZ4Ono5tZKMr1SQ==",
+ "deprecated": "This package is no longer maintained. Please use `@apidevtools/json-schema-ref-parser` instead.",
+ "license": "MIT",
+ "dependencies": {
+ "@jsdevtools/ono": "^7.1.3",
+ "@types/json-schema": "^7.0.12",
+ "call-me-maybe": "^1.0.1",
+ "js-yaml": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@readme/openapi-parser": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@readme/openapi-parser/-/openapi-parser-2.7.0.tgz",
+ "integrity": "sha512-P8WSr8WTOxilnT89tcCRKWYsG/II4sAwt1a/DIWub8xTtkrG9cCBBy/IUcvc5X8oGWN82MwcTA3uEkDrXZd/7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@apidevtools/swagger-methods": "^3.0.2",
+ "@jsdevtools/ono": "^7.1.3",
+ "@readme/better-ajv-errors": "^2.0.0",
+ "@readme/json-schema-ref-parser": "^1.2.0",
+ "@readme/openapi-schemas": "^3.1.0",
+ "ajv": "^8.12.0",
+ "ajv-draft-04": "^1.0.0",
+ "call-me-maybe": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "openapi-types": ">=7"
+ }
+ },
+ "node_modules/@readme/openapi-schemas": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@readme/openapi-schemas/-/openapi-schemas-3.1.0.tgz",
+ "integrity": "sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@types/bun": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.14.tgz",
+ "integrity": "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bun-types": "1.2.14"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.17.50",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz",
+ "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-draft-04": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^8.5.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/bun-types": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.14.tgz",
+ "integrity": "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/call-me-maybe": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
+ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
+ "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/jsonpointer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
+ "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/openapi-types": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/prettier": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ }
+ }
+}
diff --git a/src/SchemaParser.ts b/src/SchemaParser.ts
index a59e6d4..850f11f 100644
--- a/src/SchemaParser.ts
+++ b/src/SchemaParser.ts
@@ -21,9 +21,43 @@ type Response = OpenAPIV3_1.ResponseObject
type Schema = IJsonSchema | OpenAPIV3_1.BaseSchemaObject | IJsonSchema[] | OpenAPIV3.BaseSchemaObject
type RequestBody = OpenAPIV3_1.RequestBodyObject
+export interface SchemaParserOptions {
+ /**
+ * Préfixe pour les types générés
+ * @default "API"
+ */
+ typePrefix?: string
+ /**
+ * Inclure les exemples de requête dans les commentaires
+ * @default false
+ */
+ includeExamples?: boolean
+ /**
+ * Générer des types pour les corps de requête
+ * @default true
+ */
+ includeRequestBodies?: boolean
+ /**
+ * Générer des types pour les réponses
+ * @default true
+ */
+ includeResponses?: boolean
+}
+
export class SchemaParser {
+ // Options par défaut qui seront utilisées si aucune n'est fournie
+ private options: SchemaParserOptions = {
+ typePrefix: 'API',
+ includeExamples: false,
+ includeRequestBodies: true,
+ includeResponses: true
+ }
- constructor (private document: Document) {
+ constructor (private document: Document, options?: SchemaParserOptions) {
+ // Si des options sont fournies, les fusionner avec les options par défaut
+ if (options) {
+ this.options = { ...this.options, ...options }
+ }
}
convertToCode (): string {
@@ -35,7 +69,7 @@ export class SchemaParser {
if (group === 'securitySchemes') {
continue
}
- const typeName = 'API' + capitalize(group)
+ const typeName = this.options.typePrefix + capitalize(group)
const components = new ObjectType()
for (const [schemaName, componentSchema] of Object.entries(schemas)) {
components.addProperty(schemaName, this.itemToNode(componentSchema?.['content']?.['application/json']?.['schema'] ?? componentSchema))
@@ -60,13 +94,13 @@ export class SchemaParser {
apiEndpoint.addProperty('requests', requests)
apiEndpoints.addProperty(endpoint, apiEndpoint)
}
- types.push(['APIEndpoints', apiEndpoints])
+ types.push([this.options.typePrefix + 'Endpoints', apiEndpoints])
return types.map(([name, type]) => `export type ${name} = ${type.toString()}`).join('\n\n') + `
-export type APIPaths = keyof APIEndpoints
+export type APIPaths = keyof ${this.options.typePrefix}Endpoints
-export type APIRequests = APIEndpoints[T]["requests"]
+export type APIRequests = ${this.options.typePrefix}Endpoints[T]["requests"]
export type APIMethods = NonNullable["method"]>
@@ -84,8 +118,8 @@ type DefaultToGet = T extends string
export type APIResponse<
T extends APIPaths,
M extends string | undefined
-> = DefaultToGet extends keyof APIEndpoints[T]["responses"]
- ? APIEndpoints[T]["responses"][DefaultToGet]
+> = DefaultToGet extends keyof ${this.options.typePrefix}Endpoints[T]["responses"]
+ ? ${this.options.typePrefix}Endpoints[T]["responses"][DefaultToGet]
: never`
}
@@ -236,7 +270,7 @@ export type APIResponse<
if ('$ref' in item && item.$ref) {
const [_, __, group, name] = item.$ref.split('/')
- return new SimpleType(`API${capitalize(group)}['${name}']`).with(infos)
+ return new SimpleType(`${this.options.typePrefix}${capitalize(group)}['${name}']`).with(infos)
}
if ('anyOf' in item && item.anyOf) {
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..71d59d7
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,123 @@
+import { existsSync, readFileSync } from 'node:fs'
+import path from 'node:path'
+
+export interface O2TSConfig {
+ /**
+ * Format du code généré
+ */
+ format?: {
+ /**
+ * Ajouter des points-virgules à la fin des lignes
+ * @default false
+ */
+ semi?: boolean
+ /**
+ * Largeur d'indentation
+ * @default 2
+ */
+ tabWidth?: number
+ /**
+ * Utiliser des simples quotes au lieu de doubles
+ * @default true
+ */
+ singleQuote?: boolean
+ /**
+ * Ajouter des virgules finales
+ * @default true
+ */
+ trailingComma?: boolean
+ }
+ /**
+ * Options du générateur
+ */
+ generator?: {
+ /**
+ * Préfixe pour les types générés
+ * @default "API"
+ */
+ typePrefix?: string
+ /**
+ * Inclure les exemples de requête dans les commentaires
+ * @default false
+ */
+ includeExamples?: boolean
+ /**
+ * Générer des types pour les corps de requête
+ * @default true
+ */
+ includeRequestBodies?: boolean
+ /**
+ * Générer des types pour les réponses
+ * @default true
+ */
+ includeResponses?: boolean
+ }
+}
+
+export const DEFAULT_CONFIG: O2TSConfig = {
+ format: {
+ semi: false,
+ tabWidth: 2,
+ singleQuote: true,
+ trailingComma: true
+ },
+ generator: {
+ typePrefix: 'API',
+ includeExamples: false,
+ includeRequestBodies: true,
+ includeResponses: true
+ }
+}
+
+/**
+ * Récupère la configuration de o2ts
+ *
+ * Cherche dans l'ordre:
+ * 1. Fichier .o2tsrc.json à la racine du projet
+ * 2. Section "o2ts" dans package.json
+ * 3. Configuration par défaut
+ */
+export function getConfig(basePath: string = process.cwd()): O2TSConfig {
+ // Chercher un fichier de configuration .o2tsrc.json
+ const configPath = path.join(basePath, '.o2tsrc.json')
+ if (existsSync(configPath)) {
+ try {
+ const configContent = readFileSync(configPath, 'utf-8')
+ return mergeConfig(JSON.parse(configContent))
+ } catch (error) {
+ console.warn(`Erreur lors de la lecture du fichier .o2tsrc.json: ${error}`)
+ }
+ }
+
+ // Chercher une section o2ts dans package.json
+ const packagePath = path.join(basePath, 'package.json')
+ if (existsSync(packagePath)) {
+ try {
+ const packageContent = readFileSync(packagePath, 'utf-8')
+ const packageData = JSON.parse(packageContent)
+ if (packageData.o2ts && typeof packageData.o2ts === 'object') {
+ return mergeConfig(packageData.o2ts)
+ }
+ } catch (error) {
+ console.warn(`Erreur lors de la lecture de la configuration dans package.json: ${error}`)
+ }
+ }
+
+ return DEFAULT_CONFIG
+}
+
+/**
+ * Fusionne la configuration utilisateur avec la configuration par défaut
+ */
+function mergeConfig(userConfig: Partial): O2TSConfig {
+ return {
+ format: {
+ ...DEFAULT_CONFIG.format,
+ ...userConfig.format
+ },
+ generator: {
+ ...DEFAULT_CONFIG.generator,
+ ...userConfig.generator
+ }
+ }
+}
diff --git a/src/generator.ts b/src/generator.ts
index 6b71247..6f2f7de 100644
--- a/src/generator.ts
+++ b/src/generator.ts
@@ -5,15 +5,31 @@ import type { OpenAPIV3_1 } from 'openapi-types'
import { format } from 'prettier'
import { writeFileSync } from 'node:fs'
import { SchemaParser } from './SchemaParser.js'
+import { getConfig } from './config.js'
try {
const args = process.argv.slice(2)
const yamlFile = args[0] ?? './openapi.yml'
- const tsFile = args[1] ? args[1] : args[0].replace('.yml', '.ts').replace('.yaml', '.ts')
+ const tsFile = args[1] ? args[1] : yamlFile.replace(/\.ya?ml$/, '.ts')
+
+ // Récupérer la configuration
+ const projectRoot = process.cwd()
+ const config = getConfig(projectRoot)
+
const apiSchema = await OpenAPI.parse(yamlFile) as OpenAPIV3_1.Document;
- const options = new SchemaParser(apiSchema)
+ const options = new SchemaParser(apiSchema, config.generator)
const code = options.convertToCode()
- writeFileSync(tsFile, await format(code, { semi: false, parser: "typescript" }))
+
+ // Utiliser les options de format de la configuration
+ const formatOptions = {
+ parser: "typescript",
+ semi: config.format?.semi ?? false,
+ tabWidth: config.format?.tabWidth ?? 2,
+ singleQuote: config.format?.singleQuote ?? true,
+ trailingComma: (config.format?.trailingComma === true ? "all" : "none") as "all" | "none" | "es5"
+ }
+
+ writeFileSync(tsFile, await format(code, formatOptions))
process.stdout.write(`${tsFile} created with success`)
process.exit(0)
}
diff --git a/tests/generator.test.ts b/tests/generator.test.ts
index a800849..0448ad6 100644
--- a/tests/generator.test.ts
+++ b/tests/generator.test.ts
@@ -9,14 +9,19 @@ import {format} from 'prettier'
const testDir = dirname(fileURLToPath(import.meta.url))
+// Fonction pour normaliser les sauts de ligne (convertir CRLF et LF en LF)
+const normalizeLine = (text: string) => text.replace(/\r\n/g, "\n");
+
const shouldMatchFiles = async (file: string) => {
const apiSchema = await OpenAPI.parse(join(testDir, `${file}.yml`)) as OpenAPIV3_1.Document;
+ // Utiliser la nouvelle signature de SchemaParser en passant null pour les options
const options = new SchemaParser(apiSchema)
const code = options.convertToCode()
const got = await format(code, {semi: false, parser: "typescript"})
const fixturePath = join(testDir, `${file}.ts`)
const want = await Bun.file(fixturePath).text()
- expect(got).toBe(want)
+ // Normaliser les sauts de ligne avant de comparer
+ expect(normalizeLine(got)).toBe(normalizeLine(want))
}
test('it should work with OpenAPIV3.1', () => {