@team-supercharge/oasg 17.1.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +260 -47
  2. package/bin/oasg +23 -4
  3. package/bin/save-lock.js +101 -0
  4. package/bin/target-version.js +84 -0
  5. package/config.schema.yml +15 -0
  6. package/package.json +1 -1
  7. package/targets/android/generate.sh +2 -2
  8. package/targets/android/generator-config.json +2 -1
  9. package/targets/android/publish.sh +1 -1
  10. package/targets/android/templates/build.gradle.mustache +63 -46
  11. package/targets/angular/generate.sh +35 -9
  12. package/targets/angular/publish.sh +1 -1
  13. package/targets/apple-swift/generate.sh +1 -1
  14. package/targets/apple-swift/publish.sh +1 -1
  15. package/targets/common.sh +3 -1
  16. package/targets/contract-testing/generate.sh +1 -1
  17. package/targets/contract-testing/publish.sh +1 -1
  18. package/targets/dependency-lock-utils.sh +290 -0
  19. package/targets/feign/generate.sh +1 -1
  20. package/targets/feign/publish.sh +1 -1
  21. package/targets/feign-kotlin/generate.sh +1 -1
  22. package/targets/feign-kotlin/publish.sh +1 -1
  23. package/targets/flutter/generate.sh +1 -1
  24. package/targets/flutter/publish.sh +1 -1
  25. package/targets/ios/generate.sh +1 -1
  26. package/targets/ios/publish.sh +1 -1
  27. package/targets/kmp/generate.sh +1 -1
  28. package/targets/kmp/publish.sh +1 -1
  29. package/targets/kmp/templates/build.gradle.kts.mustache +17 -13
  30. package/targets/kmp/templates/libraries/multiplatform/api.mustache +2 -0
  31. package/targets/msw/generate.sh +25 -0
  32. package/targets/msw/generator-config.json +28 -0
  33. package/targets/msw/templates/apis.index.mustache +9 -0
  34. package/targets/msw/templates/apis.mustache +178 -0
  35. package/targets/msw/templates/config.mustache +23 -0
  36. package/targets/msw/templates/index.mustache +11 -0
  37. package/targets/msw/templates/modelEnum.mustache +1 -0
  38. package/targets/msw/templates/modelGeneric.mustache +7 -0
  39. package/targets/msw/templates/package.mustache +35 -0
  40. package/targets/msw/templates/prettierrc.mustache +6 -0
  41. package/targets/msw/templates/tsconfig.mustache +18 -0
  42. package/targets/msw/templates/utils.mustache +15 -0
  43. package/targets/nestjs/generate.sh +36 -4
  44. package/targets/nestjs/publish.sh +1 -1
  45. package/targets/nestjs/templates/api.service.mustache +8 -14
  46. package/targets/plain-java/generate.sh +1 -1
  47. package/targets/plain-java/publish.sh +1 -1
  48. package/targets/python/generate.sh +1 -1
  49. package/targets/python/publish.sh +1 -1
  50. package/targets/python-fastapi/generate.sh +1 -1
  51. package/targets/python-fastapi/publish.sh +1 -1
  52. package/targets/python-fastapi-raw-request/generate.sh +1 -1
  53. package/targets/python-fastapi-raw-request/publish.sh +1 -1
  54. package/targets/python-legacy/generate.sh +1 -1
  55. package/targets/python-legacy/publish.sh +1 -1
  56. package/targets/react/generate.sh +17 -5
  57. package/targets/react/publish.sh +1 -1
  58. package/targets/react/templates/hook.mustache +172 -51
  59. package/targets/react/templates/package.mustache +1 -1
  60. package/targets/react/templates/use-api.hook.mustache +2 -0
  61. package/targets/spring/generate.sh +1 -1
  62. package/targets/spring/publish.sh +1 -1
  63. package/targets/spring-kotlin/generate.sh +1 -1
  64. package/targets/spring-kotlin/publish.sh +1 -1
  65. package/targets/stubby/generate.sh +1 -1
  66. package/targets/stubby/publish.sh +1 -1
  67. package/targets/typescript-axios/generate.sh +16 -5
  68. package/targets/typescript-axios/publish.sh +1 -1
  69. package/targets/typescript-fetch/generate.sh +17 -5
  70. package/targets/typescript-fetch/publish.sh +1 -1
  71. package/targets/android/templates/libraries/jvm-retrofit2/api.mustache +0 -157
  72. package/targets/android/templates/libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache +0 -354
package/README.md CHANGED
@@ -128,38 +128,39 @@ include:
128
128
 
129
129
  The table below gives an overview of the changes (breaking, non-breaking, bug fixes) introduced in various major versions. For the resolution of breaking changes please consult the [Migration Guide](#migration-guide).
130
130
 
131
- | Component | | | | | | | | | | | | | | | | | |
132
- |------------------------------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
133
- | **Internal** | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
134
- | _Core_ |💥 |✨ |🐛 |💥 |✨ |🐛 |✨ |➖ |✨ |🐛 |🐛 |➖ |💥 |💥 |✨ |💥 |🆕 |
135
- | _Linter_ |➖ |🐛 |➖ |➖ |➖ |➖ |💥 |➖ |➖ |➖ |➖ |🐛 |➖ |✨ |💥 |🆕 |
136
- | **Client Targets** | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
137
- | `android` |➖ |➖ |➖ |🐛 |💥 |➖ |➖ |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
138
- | `angular` |➖ |✨ |➖ |🐛 |💥 |➖ |➖ |➖ |🐛 |➖ |➖ |➖ |💥 |➖ |➖ |➖ |🆕 |
139
- | `dotnet` |➖ |➖ |➖ |🆕 |
140
- | `feign` |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |🐛 |🐛 |🆕 |
141
- | `feign-kotlin` |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |🆕 |
142
- | `plain-java` |🐛 |🆕 |
143
- | `flutter` |➖ |➖ |➖ |➖ |🆕 |
144
- | `ios` |➖ |➖ |➖ |➖ |💥 |🐛 |➖ |➖ |➖ |✨ |➖ |💥 |➖ |➖ |✨ |🆕 |
145
- | `apple-swift` |➖ |🆕 |
146
- | `kmp` |✨ |➖ |➖ |🆕 |
147
- | `python` |➖ |🆕 |
148
- | `python-legacy` |➖ |💥 |➖ |🐛 |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
149
- | `react` |➖ |🐛 |🐛 |➖ |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
150
- | `typescript-axios` |➖ |🆕 |
151
- | `typescript-fetch` |➖ |🆕 |
152
- | **Server Targets** | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
153
- | `nestjs` |🐛 |💥 |💥 |➖ |💥 |✨ |➖ |➖ |🐛 |➖ |➖ |✨ |🆕 |
154
- | `python-fastapi` |➖ |➖ |➖ |🆕 |
155
- | `python-fastapi-raw-request` |➖ |➖ |➖ |🆕 |
156
- | `spring` |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |➖ |✨ |➖ |🆕 |
157
- | `spring-kotlin` |✨ |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |🐛 |✨ |➖ |🆕 |
158
- | **Misc Targets** | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
159
- | `contract-testing` |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
160
- | `openapi` |➖ |➖ |➖ |➖ |➖ |💥 |➖ |➖ |✨ |➖ |🆕 |
161
- | `stubby` |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |💥 |➖ |➖ |➖ |➖ |🆕 |
162
- | `postman` |➖ |➖ |🆕 |
131
+ | Component | | | | | | | | | | | | | | | | | | |
132
+ |------------------------------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
133
+ | **Internal** | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
134
+ | _Core_ |✨ |💥 |✨ |🐛 |💥 |✨ |🐛 |✨ |➖ |✨ |🐛 |🐛 |➖ |💥 |💥 |✨ |💥 |🆕 |
135
+ | _Linter_ |➖ |➖ |🐛 |➖ |➖ |➖ |➖ |💥 |➖ |➖ |➖ |➖ |🐛 |➖ |✨ |💥 |🆕 |
136
+ | **Client Targets** | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
137
+ | `android` |✨ |➖ |➖ |➖ |🐛 |💥 |➖ |➖ |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
138
+ | `angular` |➖ |➖ |✨ |➖ |🐛 |💥 |➖ |➖ |➖ |🐛 |➖ |➖ |➖ |💥 |➖ |➖ |➖ |🆕 |
139
+ | `dotnet` |➖ |➖ |➖ |➖ |🆕 |
140
+ | `feign` |➖ |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |🐛 |🐛 |🆕 |
141
+ | `feign-kotlin` |➖ |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |🆕 |
142
+ | `plain-java` |➖ |🐛 |🆕 |
143
+ | `flutter` |➖ |➖ |➖ |➖ |➖ |🆕 |
144
+ | `ios` |➖ |➖ |➖ |➖ |➖ |💥 |🐛 |➖ |➖ |➖ |✨ |➖ |💥 |➖ |➖ |✨ |🆕 |
145
+ | `apple-swift` |➖ |➖ |🆕 |
146
+ | `kmp` |✨ |✨ |➖ |➖ |🆕 |
147
+ | `python` |➖ |➖ |🆕 |
148
+ | `python-legacy` |➖ |➖ |💥 |➖ |🐛 |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
149
+ | `react` |💥 |➖ |🐛 |🐛 |➖ |💥 |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
150
+ | `typescript-axios` |✨ |➖ |🆕 |
151
+ | `typescript-fetch` |✨ |➖ |🆕 |
152
+ | **Server Targets** | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
153
+ | `nestjs` |✨ |🐛 |💥 |💥 |➖ |💥 |✨ |➖ |➖ |🐛 |➖ |➖ |✨ |🆕 |
154
+ | `python-fastapi` |➖ |➖ |➖ |➖ |🆕 |
155
+ | `python-fastapi-raw-request` |➖ |➖ |➖ |➖ |🆕 |
156
+ | `spring` |➖ |🐛 |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |➖ |✨ |➖ |🆕 |
157
+ | `spring-kotlin` |➖ |✨ |✨ |➖ |➖ |➖ |➖ |➖ |✨ |💥 |💥 |➖ |➖ |🐛 |✨ |➖ |🆕 |
158
+ | **Misc Targets** | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
159
+ | `contract-testing` |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |🆕 |
160
+ | `openapi` |➖ |➖ |➖ |➖ |➖ |➖ |💥 |➖ |➖ |✨ |➖ |🆕 |
161
+ | `stubby` |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |➖ |💥 |➖ |➖ |➖ |➖ |🆕 |
162
+ | `postman` |➖ |➖ |➖ |🆕 |
163
+ | `msw` |🆕 |
163
164
 
164
165
  **Legend:**
165
166
 
@@ -218,6 +219,15 @@ Publishes the generated API client package of the specified target. If no target
218
219
  $ npx oasg publish [target]
219
220
  ```
220
221
 
222
+ ## `save-lock`
223
+
224
+ Saves the current state of the package-lock.json file to the `.oasg` directory. This is useful for later restoring the lock file in case of a failed install or build.
225
+ This command is offered for `npm` based projects only to ensure that a working `package-lock.json` file is always available for the project.
226
+
227
+ ```shell
228
+ $ npx oasg save-lock [target]
229
+ ```
230
+
221
231
  ---
222
232
 
223
233
  # Linter Rules
@@ -522,20 +532,6 @@ Common target parameters
522
532
  | packageName | Name of the generated NPM package | Y | - |
523
533
  | repository | URL of the NPM package registry | Y | - |
524
534
 
525
- ##### Known issue: breaking dependencies
526
-
527
- The Angular target has a known issue related to dependency versioning. The generated Angular project's `package.json` currently defines multiple dependencies with flexible versions (e.g., using `^` or `~` in version constraints). This flexibility allows newer versions of dependencies (not to mention the transitive dependencies), to be pulled in, which can occasionally lead to breaking changes.
528
-
529
- A notable instance of this issue occurred with **Angular version 12**, which introduced changes that broke the build of generated projects. To address this specific case, we implemented a **temporary fix** in the `targets/angular/generate.sh` script. This script now manually installs problematic dependencies with fixed, validated versions.
530
-
531
- ###### Long-Term Solution
532
-
533
- We are actively discussing a long-term solution to this issue. Potential approaches include implementing dependency version locking mechanisms (`package-lock.json`) per Angular versions.
534
-
535
- The temporary fix may not cover all future breaking changes from other dependencies. Projects using different versions of Angular may encounter issues if the fix does not align with their specific requirements.
536
-
537
- We welcome feedback and contributions to help address this issue.
538
-
539
535
  #### `react`
540
536
 
541
537
  ```json
@@ -553,7 +549,132 @@ We welcome feedback and contributions to help address this issue.
553
549
  | packageName | Name of the generated NPM package | Y | - |
554
550
  | repository | URL of the NPM package registry | Y | - |
555
551
 
556
- > ℹ️ This package offers React Hooks for convenience.
552
+ **Usage**<br>
553
+
554
+ 1. Provide API config and instances
555
+
556
+ Let's create a component to setup API configuration and the APIs you need. Wrap your application with this components afterwards.
557
+
558
+ ```tsx
559
+ import { type Type, type APIS, type ConfigurationParameters, ApiConfigProvider, ApiInstancesProvider, UsersApi } from '@project/oasg-example-react';
560
+
561
+ // Provide here all the APIs you need (might change during the project)
562
+ const providedApis: Type<APIS>[] = [UsersApi];
563
+
564
+ const ApiProvider = ({ children }) => {
565
+ const config = useMemo<ConfigurationParameters>(
566
+ () => ({
567
+ basePath: 'https://your-api-url.domain',
568
+ middlewares: [/* Your middlewares, eg. adding authorization header */]
569
+ }),
570
+ []
571
+ );
572
+
573
+ return (
574
+ <ApiConfigProvider config={config}>
575
+ <ApiInstancesProvider apis={providedApis}>{children}</ApiInstancesProvider>
576
+ </ApiConfigProvider>
577
+ );
578
+ };
579
+ ```
580
+
581
+ 2. Using the hooks
582
+
583
+ The SDK exposes two React hooks for each endpoints: a query and a mutation. Their name come from the `operationId` you provided for them in the OpenAPI files. Eg.`useGetUsersQuery` and `useGetUsersMutation`.
584
+
585
+ Note: While both hooks are generated for all endpoints, we recommend using queries for GET requests and mutations for POST/PUT/PATCH/DELETE requests.
586
+
587
+ They can be used in a very similar way like `useQuery` and `useMutation` from `@tanstack/react-query` but they provide:
588
+ - built-in `queryFn`s
589
+ - built-in `queryKey`s
590
+ - typed input parameters and outputs
591
+
592
+ To fetch the list of users from the above examples is as simple as:
593
+
594
+ ```ts
595
+ const usersQuery = useGetUsersQuery();
596
+ // ^- usersQuery.data: GetUsersListResponse | undefined
597
+ ```
598
+
599
+ Or if your query has parameters they be passed as the first parameter:
600
+ ```ts
601
+ const usersQuery = useGetUsersQuery({ offset: 15, limit: 10 });
602
+ ```
603
+
604
+ 3. Query options
605
+
606
+ Query options are available as the first or the second parameter of the query, depending on if the query has parameters or not.
607
+
608
+ ```ts
609
+ const usersQuery = useGetUsersQuery({ refetchInterval: 5000 });
610
+ ```
611
+
612
+ or
613
+ ```ts
614
+ const usersQuery = useGetUsersQuery({ offset: 15, limit: 10 }, { refetchInterval: 5000 });
615
+ ```
616
+
617
+ 4. Managing query keys
618
+
619
+ You don't have to and also you can't provide `queryFn` or the `queryKey` options for use*Query hooks, as the SDK handles them automatically. You might still need to access the automatically generated query keys, so the SDK also exposes query key factories that you can use to refetch/invalidate query keys. This means that the SDK is the single source of the query keys in your app.
620
+
621
+ ```tsx
622
+ const queryClient = useQueryClient();
623
+
624
+ queryClient.invalidateQueries({ queryKey: getUsersQueryKey.all() })
625
+
626
+ // or if your query has parameters
627
+ queryClient.invalidateQueries({ queryKey: getUsersQueryKey.withParams({ offset: 15, limit: 10 }) })
628
+ ```
629
+
630
+ 5. Disabled queries
631
+
632
+ Queries with parameters can also be disabled by providing `undefined` as a parameter for them. This set's `enabled` option to `false` under the hood for the query. This could help you to write dependent queries easier.
633
+
634
+ ```ts
635
+ const accountsQuery = useGetAccountsQuery();
636
+ const accountId = accountsQuery.data?.at(0)?.id;
637
+
638
+ const reportsQuery = useGetReportsQuery(accountId && { accountId });
639
+ ```
640
+
641
+ In this case `reportsQuery` is not running until `accountsQuery` finished fetching.
642
+
643
+ 6. Passing headers to queries and mutations
644
+
645
+ `@tanstack/react-query` is not reposinble from what data sources you gather your data. This SDK does, it is deeply integrated with an underlaying API layer. So we added support to add headers to your requests.
646
+
647
+ ```ts
648
+ // HEADERS is a JavaScript symbol provided by the SDK. By its unique nature it makes sure that it doesn't interfere with the other parameters you pass to your queries.
649
+ import { HEADERS } from '@project/oasg-example-react';
650
+
651
+ // Use with queries:
652
+ const usersQuery = useGetUsersQuery({
653
+ [HEADERS]: {
654
+ 'X-Users-Feature-Flag': 'B',
655
+ }
656
+ })
657
+
658
+ // With mutations - headers can be passed at the hook level:
659
+ const createUserMutation = useCreateUserMutation({
660
+ [HEADERS]: {
661
+ 'X-Users-Feature-Flag': 'A',
662
+ }
663
+ });
664
+
665
+ // Or while calling `mutate` or `mutateAsync`:
666
+ createUserMutation.mutate({
667
+ createUserRequest: userData,
668
+ [HEADERS]: {
669
+ 'X-Users-Feature-Flag': 'B',
670
+ }
671
+ })
672
+
673
+ // Headers are merged in this order (later overrides earlier):
674
+ // 1. middleware configuration
675
+ // 2. mutation hook options
676
+ // 3. mutate/mutateAsync parameters
677
+ ```
557
678
 
558
679
  #### `stubby`
559
680
 
@@ -1117,12 +1238,104 @@ Validations from OpenAPI spec:
1117
1238
  | packageName | Name of the generated NPM package | Y | - |
1118
1239
  | repository | URL of the NPM package registry | Y | - |
1119
1240
 
1241
+ #### `msw`
1242
+
1243
+ ```json
1244
+ {
1245
+ "id": "msw",
1246
+ "type": "msw",
1247
+ "source": "source-merged",
1248
+ "packageName": "@project/oasg-example-msw",
1249
+ "repository": "https://gitlab.supercharge.io/api/v4/projects/1226/packages/npm/"
1250
+ }
1251
+ ```
1252
+
1253
+ |Parameter| Description| Required | Default |
1254
+ |-|-|-|-|
1255
+ | packageName | Name of the generated NPM package | Y | - |
1256
+ | repository | URL of the NPM package registry | Y | - |
1257
+
1258
+ **Usage**<br>
1259
+
1260
+ 1. Configure the SDK
1261
+
1262
+ Start by configuring the SDK with a `baseUrl` and an optional `delay`:
1263
+
1264
+ ```ts
1265
+ import { setMswSdkConfig } from '@project/oasg-example-msw';
1266
+
1267
+ setMswSdkConfig({
1268
+ baseUrl: 'https://your-api-url.domain',
1269
+ // each request will take at least 100ms and at most 400ms (this is the default)
1270
+ // disable delay by setting it to [0, 0]
1271
+ delay: [100, 400],
1272
+ });
1273
+ ```
1274
+
1275
+ 2. Mock your endpoints
1276
+
1277
+ Once configured, you can start mocking your endpoints.
1278
+ The SDK automatically sets up the handlers and enforces fully typed params, query params, request bodies, and responses based on your OpenAPI spec.
1279
+
1280
+ ```ts
1281
+ import { setupWorker } from 'msw/browser';
1282
+ import { HttpReponse } from 'msw';
1283
+ import { mockUsersApi, type User } from '@project/oasg-example-msw';
1284
+
1285
+ const mockUsers: User[] = [
1286
+ {
1287
+ id: crypto.randomUUID(),
1288
+ firstName: 'Chester',
1289
+ lastName: 'Bennington',
1290
+ createdAt: new Date(),
1291
+ },
1292
+ {
1293
+ id: crypto.randomUUID(),
1294
+ firstName: 'Emily',
1295
+ lastName: 'Armstrong',
1296
+ createdAt: new Date(),
1297
+ }
1298
+ ];
1299
+
1300
+ setupWorker(
1301
+ // each tag in your OpenAPI spec exposes a `mock{ApiName}Api` helper
1302
+ ...mockUsersApi({
1303
+ // provide the endpoints you want to mock by their `operationId`
1304
+ getUsers: (info) => {
1305
+ return HttpResponse({
1306
+ items: info.queryParams.sortDirection === 'desc'
1307
+ ? mockUsers.toSorted((a, b) => a.createdAt - b.createdAt)
1308
+ : mockUsers.toSorted((a, b) => b.createdAt - a.createdAt),
1309
+ })
1310
+ },
1311
+ createUser: (info) => {
1312
+ const user = await info.request.json();
1313
+ mockUsers.push({
1314
+ id: crypto.randomUUID(),
1315
+ createdAt: new Date(),
1316
+ ...user,
1317
+ });
1318
+
1319
+ return HttpResponse(null, { status: 201 });
1320
+ }
1321
+ }),
1322
+ )
1323
+ ```
1324
+
1120
1325
  ---
1121
1326
 
1122
1327
  # Migration Guide
1123
1328
 
1124
1329
  This section covers the breaking changes and their migrations across major version upgrades.
1125
1330
 
1331
+ ## From `17.x.x` to `18.0.0`
1332
+
1333
+ ### Breaking in `react` target
1334
+
1335
+ 1. Exported hooks were renamed. API names are not part of the hooks anymore as `operationId` is unique by itself. E.g. instead of `useUsersApiCreateUserMutation` the name is `useCreateApiMutation`.
1336
+ 2. *Query keys* are now managed by the SDK itself and they were removed from the query's options object. Check the *Managing query keys* section below to get an idea of how you can still work with query keys in your application.
1337
+ 3. If an endpoint has URL parameters, search parameters, or a body, it is now the first parameter of the `use*Query` hooks. E.g. instead of `useGetUserList({ refetchInterval: 60_000 }, { limit, offset })` it is `useGetUserList({ limit, offset }, { refetchInterval: 60_000 })`. This especially comes in handy together with the automatic queryKey management the SDK provides, as without the queryKey there are no required parameters in the query's options object, so you can completely ignore it if you don't want to set any options. E.g. instead of `useGetUserList({}, { limit, offset })` it is `useGetUserList({ limit, offset })` now.
1338
+
1126
1339
  ## From `16.x.x` to `17.0.0`
1127
1340
 
1128
1341
  From this version _OASg_ requires `node` version `^20.19` by default. Please upgrade your projects accordingly.
package/bin/oasg CHANGED
@@ -21,6 +21,7 @@ const { applyOverrides } = require(`${__dirname}/overrider.js`);
21
21
  const { openApiTarget } = require(`${__dirname}/openapi-target.js`);
22
22
  const { postmanTarget } = require(`${__dirname}/postman-target.js`);
23
23
  const { processSource } = require(`${__dirname}/process-source.js`);
24
+ const { saveLock } = require(`${__dirname}/save-lock.js`);
24
25
  const { globSync } = require('glob');
25
26
 
26
27
  const projectPackageJson = JSON.parse(fs.readFileSync('package.json'));
@@ -35,7 +36,7 @@ const PROXY_PORT = '9999';
35
36
 
36
37
  const DEFAULT_GENERATOR_MAPPING = {
37
38
  // client targets
38
- "android": { version: '7.0.1', generator: 'kotlin' },
39
+ "android": { version: '7.17.0', generator: 'kotlin' },
39
40
  "angular": { version: '7.11.0', generator: 'typescript-angular' },
40
41
  "feign": { version: '7.12.0', generator: 'spring' },
41
42
  "feign-kotlin": { version: '7.12.0', generator: 'kotlin-spring' },
@@ -43,7 +44,7 @@ const DEFAULT_GENERATOR_MAPPING = {
43
44
  "flutter": { version: '7.0.1', generator: 'dart-dio' },
44
45
  "ios": { version: '7.0.1', generator: 'swift5' },
45
46
  "apple-swift": { version: undefined, generator: undefined },
46
- "kmp": { version: '7.14.0', generator: 'kotlin' },
47
+ "kmp": { version: '7.17.0', generator: 'kotlin' },
47
48
  "python": { version: '7.11.0', generator: 'python' },
48
49
  "python-legacy": { version: '7.11.0', generator: 'python-pydantic-v1' },
49
50
  "react": { version: '7.0.1', generator: 'typescript-fetch' },
@@ -62,6 +63,7 @@ const DEFAULT_GENERATOR_MAPPING = {
62
63
  "openapi": { version: undefined, generator: undefined },
63
64
  "stubby": { version: '4.3.1', generator: 'stubby' },
64
65
  "postman": { version: undefined, generator: undefined },
66
+ "msw": { version: '7.11.0', generator: 'typescript-fetch' },
65
67
  };
66
68
  const DEFAULT_KTLINT_VERSION = '1.0.0';
67
69
  const BIN_FOLDER = 'out/.bin';
@@ -134,8 +136,23 @@ async function run() {
134
136
  describe: 'Generate as pre-release (optional). \nHow a pre-release is handled varies between different target types.',
135
137
  type: 'boolean'
136
138
  })
139
+ .option('force-lock', {
140
+ describe: 'Force usage of saved package-lock.json (skip npm install, use saved lock immediately)',
141
+ type: 'boolean',
142
+ default: false
143
+ })
137
144
  }, (argv) => generate(argv))
138
145
 
146
+ // save-lock
147
+ .command(['save-lock [target]', 'sl'], 'save package-lock.json for a target', (yargs) => {
148
+ yargs
149
+ .positional('target', {
150
+ describe: 'specify the target name',
151
+ type: 'string',
152
+ demandOption: true
153
+ })
154
+ }, (argv) => saveLock(argv, config, checkTargetId))
155
+
139
156
  // publish
140
157
  .command(['publish [target]', 'p'], 'publish packages', (yargs) => {
141
158
  yargs
@@ -159,7 +176,7 @@ function checkVersionMismatch() {
159
176
  const oasgVersion = oasgPackageJson.version;
160
177
  const oasgDepVersion = projectPackageJson.dependencies[oasgPackageJson.name];
161
178
 
162
- if (oasgDepVersion.startsWith('file:')) {
179
+ if (!oasgDepVersion || oasgDepVersion.startsWith('file:')) {
163
180
  return;
164
181
  }
165
182
 
@@ -477,6 +494,7 @@ async function generate(argv) {
477
494
  const targetIds = determineTargetIds(argv);
478
495
  VERSION = determineArtifactVersion(argv);
479
496
  const preRelease = determinePreRelease(argv);
497
+ const forceLock = argv['force-lock'] || argv.forceLock || false;
480
498
 
481
499
  const sources = await buildSources(sourceIdsFromTargetIds(targetIds));
482
500
 
@@ -509,7 +527,7 @@ async function generate(argv) {
509
527
  const templateDir = customizeTemplates(target);
510
528
 
511
529
  // run generation
512
- exec(`bash -e ${__dirname}/../targets/${target.type}/generate.sh ${VERSION} ${binary} ${CONFIG_FILE_NAME} ${target.id} ${sources[target.source]} ${formatter} ${target.generatorId} ${preRelease.toString()} ${templateDir}`);
530
+ exec(`bash -e ${__dirname}/../targets/${target.type}/generate.sh ${VERSION} ${binary} ${CONFIG_FILE_NAME} ${target.id} ${sources[target.source]} ${formatter} ${target.generatorId} ${preRelease.toString()} ${templateDir} ${forceLock.toString()}`);
513
531
  });
514
532
  }
515
533
 
@@ -721,3 +739,4 @@ function determineArtifactVersion(argv) {
721
739
  function determinePreRelease(argv) {
722
740
  return argv.preRelease ? true : false;
723
741
  }
742
+
@@ -0,0 +1,101 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { exit } = require('process');
4
+
5
+ const { getTargetVersion } = require(`${__dirname}/target-version.js`);
6
+
7
+ async function saveLock(argv, config, checkTargetId) {
8
+ const { target } = argv;
9
+
10
+ // Validate target
11
+ checkTargetId(target);
12
+
13
+ // Validate target is applicable for save-lock (must be npm-based)
14
+ const targetObj = config.targets.find(t => t.id === target);
15
+ if (!targetObj) {
16
+ console.error(`Error: target '${target}' not found in config`);
17
+ exit(1);
18
+ }
19
+
20
+ const generateScriptPath = path.join(__dirname, '..', 'targets', targetObj.type, 'generate.sh');
21
+ let isNpmBased = false;
22
+ try {
23
+ if (fs.existsSync(generateScriptPath)) {
24
+ const scriptContent = fs.readFileSync(generateScriptPath, 'utf8');
25
+ // Heuristics: target is npm-based if its generate script uses npm
26
+ isNpmBased = /\bnpm\b/.test(scriptContent);
27
+ }
28
+ } catch (_) {
29
+ // ignore and treat as non-npm-based below
30
+ }
31
+
32
+ if (!isNpmBased) {
33
+ // Collect npm-capable targets to help the user
34
+ const npmTargets = config.targets
35
+ .filter(t => {
36
+ const p = path.join(__dirname, '..', 'targets', t.type, 'generate.sh');
37
+ try {
38
+ if (!fs.existsSync(p)) return false;
39
+ const s = fs.readFileSync(p, 'utf8');
40
+ return /\bnpm\b/.test(s);
41
+ } catch (_) {
42
+ return false;
43
+ }
44
+ })
45
+ .map(t => t.id);
46
+
47
+ console.error(`Error: target '${target}' is not applicable for save-lock. This command only supports npm-based targets.`);
48
+ if (npmTargets.length > 0) {
49
+ console.error(`Valid npm targets: ${npmTargets.join(', ')}`);
50
+ }
51
+ exit(1);
52
+ }
53
+
54
+ const locksDir = '.oasg';
55
+ const sourceLockFile = path.join('out', target, 'package-lock.json');
56
+
57
+ console.log(`Saving package-lock.json for ${target}...`);
58
+
59
+ // Check if source package-lock.json exists
60
+ if (!fs.existsSync(sourceLockFile)) {
61
+ console.error(`Error: package-lock.json not found at ${sourceLockFile}`);
62
+ console.error(`Please run 'oasg generate ${target}' first to create the package-lock.json file.`);
63
+ exit(1);
64
+ }
65
+
66
+ // Get target version for dependency locking using the abstraction
67
+ const targetVersion = getTargetVersion(targetObj);
68
+
69
+ // Build path based on whether we have a target version
70
+ let targetLockDir, targetLockFile;
71
+ if (targetVersion) {
72
+ targetLockDir = path.join(locksDir, `${target}-${targetVersion}`);
73
+ targetLockFile = path.join(targetLockDir, 'package-lock.json');
74
+ } else {
75
+ targetLockDir = path.join(locksDir, target);
76
+ targetLockFile = path.join(targetLockDir, 'package-lock.json');
77
+ }
78
+
79
+ // Create target directory
80
+ if (!fs.existsSync(targetLockDir)) {
81
+ fs.mkdirSync(targetLockDir, { recursive: true });
82
+ }
83
+
84
+ // Copy package-lock.json
85
+ try {
86
+ fs.copyFileSync(sourceLockFile, targetLockFile);
87
+ console.log(`✓ Package-lock.json saved to ${targetLockFile}`);
88
+ console.log('');
89
+ console.log('Next steps:');
90
+ console.log(`1. Commit the saved lock file to version control:`);
91
+ console.log(` git add ${targetLockFile}`);
92
+ console.log(` git commit -m "Add/update package-lock.json for ${target}"`);
93
+ console.log('');
94
+ console.log('2. This lock file will be used as a fallback if future builds fail due to dependency issues.');
95
+ } catch (error) {
96
+ console.error(`Error copying package-lock.json: ${error.message}`);
97
+ exit(1);
98
+ }
99
+ }
100
+
101
+ exports.saveLock = saveLock;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Utility module for extracting target versions from target configurations.
3
+ *
4
+ * This module is used exclusively by the save-lock feature for npm-based projects
5
+ * to determine version-specific lock file paths.
6
+ *
7
+ * Different target types may have different target version extraction strategies.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ /**
14
+ * Get the target version for a target.
15
+ *
16
+ * This function is used by the save-lock feature for npm-based targets to determine
17
+ * version-specific paths for storing package-lock.json files in the .oasg directory.
18
+ *
19
+ * For npm-based Angular and NestJS targets:
20
+ * - First checks if ngVersion is overridden in generatorCustomArgs (-p ngVersion=X.X.X)
21
+ * - Falls back to ngVersion in generator-config.json
22
+ *
23
+ * For other npm-based targets (react, typescript-axios, typescript-fetch, contract-testing):
24
+ * - Returns empty string (no target version, uses target id only for lock file path)
25
+ *
26
+ * @param {Object} targetObj - The target object from config
27
+ * @param {string} targetObj.type - The target type (e.g., 'angular', 'nestjs', 'react')
28
+ * @param {string} [targetObj.generatorCustomArgs] - Optional custom arguments passed to generator
29
+ * @returns {string} The target version or empty string if not applicable
30
+ */
31
+ function getTargetVersion(targetObj) {
32
+ const targetType = targetObj.type;
33
+
34
+ // Only Angular and NestJS have target versions (ngVersion)
35
+ if (targetType !== 'angular' && targetType !== 'nestjs') {
36
+ return '';
37
+ }
38
+
39
+ return getAngularNestjsTargetVersion(targetObj);
40
+ }
41
+
42
+ /**
43
+ * Get the ngVersion for Angular or NestJS npm-based targets.
44
+ *
45
+ * Used by save-lock to create version-specific lock file paths (e.g., .oasg/angular-12.0.0/).
46
+ *
47
+ * Priority: 1. generatorCustomArgs override, 2. generator-config.json default
48
+ *
49
+ * @param {Object} targetObj - The target object
50
+ * @returns {string} The ngVersion or empty string
51
+ */
52
+ function getAngularNestjsTargetVersion(targetObj) {
53
+ let targetVersion = '';
54
+
55
+ // First, check if ngVersion is overridden in generatorCustomArgs
56
+ if (targetObj.generatorCustomArgs) {
57
+ const ngVersionMatch = targetObj.generatorCustomArgs.match(/-p\s*ngVersion=(\d+\.\d+\.\d+)/);
58
+ if (ngVersionMatch) {
59
+ targetVersion = ngVersionMatch[1];
60
+ console.log(`Using ngVersion from generatorCustomArgs: ${targetVersion}`);
61
+ return targetVersion;
62
+ }
63
+ }
64
+
65
+ // Fall back to generator-config.json if not found in generatorCustomArgs
66
+ const generatorConfigPath = path.join(__dirname, '..', 'targets', targetObj.type, 'generator-config.json');
67
+ try {
68
+ if (fs.existsSync(generatorConfigPath)) {
69
+ const generatorConfig = JSON.parse(fs.readFileSync(generatorConfigPath, 'utf8'));
70
+ targetVersion = generatorConfig.ngVersion || '';
71
+ if (targetVersion) {
72
+ console.log(`Using ngVersion from generator-config.json: ${targetVersion}`);
73
+ }
74
+ }
75
+ } catch (error) {
76
+ // Silently ignore - no target version available
77
+ }
78
+
79
+ return targetVersion;
80
+ }
81
+
82
+ module.exports = {
83
+ getTargetVersion
84
+ };
package/config.schema.yml CHANGED
@@ -38,6 +38,7 @@ properties:
38
38
  - $ref: '#/targets/Postman'
39
39
  - $ref: '#/targets/TypeScriptAxios'
40
40
  - $ref: '#/targets/TypeScriptFetch'
41
+ - $ref: '#/targets/MSW'
41
42
  required:
42
43
  - targets
43
44
  additionalProperties: false
@@ -533,6 +534,20 @@ targets:
533
534
  - packageName
534
535
  - repository
535
536
 
537
+ MSW:
538
+ allOf:
539
+ - $ref: '#/targets/Base'
540
+ - properties:
541
+ type:
542
+ pattern: "^msw$"
543
+ packageName:
544
+ type: string
545
+ repository:
546
+ type: string
547
+ required:
548
+ - packageName
549
+ - repository
550
+
536
551
  definitions:
537
552
  # default
538
553
  SourceOverrides:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-supercharge/oasg",
3
- "version": "17.1.0",
3
+ "version": "18.0.0",
4
4
  "description": "Node-based tool to lint OpenAPI documents and generate clients, servers and documentation from them",
5
5
  "author": "Supercharge",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- #/bin/bash
1
+ #!/bin/bash
2
2
 
3
3
  source $(dirname "$0")/../common.sh
4
4
 
@@ -7,7 +7,7 @@ mkdir -p out/$targetId
7
7
 
8
8
  if [ -z "$formatterCustomArgs" ]
9
9
  then
10
- formatterCustomArgs="--disabled_rules=no-wildcard-imports,max-line-length,enum-entry-name-case"
10
+ formatterCustomArgs="--disabled_rules=no-wildcard-imports,max-line-length,enum-entry-name-case,filename,no-empty-file"
11
11
  fi
12
12
 
13
13
  java -jar $binary generate \