@geekmidas/cli 0.3.0 → 0.5.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.
- package/README.md +488 -71
- package/dist/CronGenerator-BPTqNYOR.d.cts +14 -0
- package/dist/{CronGenerator-Bh26MaNA.mjs → CronGenerator-CCRYptuT.mjs} +2 -2
- package/dist/{CronGenerator-Bh26MaNA.mjs.map → CronGenerator-CCRYptuT.mjs.map} +1 -1
- package/dist/{CronGenerator-C6MF8rlG.cjs → CronGenerator-D4TWXQbh.cjs} +2 -2
- package/dist/{CronGenerator-C6MF8rlG.cjs.map → CronGenerator-D4TWXQbh.cjs.map} +1 -1
- package/dist/CronGenerator-YAj59JUd.d.mts +14 -0
- package/dist/EndpointGenerator-ChAD1INz.d.cts +19 -0
- package/dist/EndpointGenerator-Cj3O1U8-.d.mts +19 -0
- package/dist/{EndpointGenerator-CWh18d92.mjs → EndpointGenerator-DGivkPLT.mjs} +77 -7
- package/dist/EndpointGenerator-DGivkPLT.mjs.map +1 -0
- package/dist/{EndpointGenerator-C73wNoih.cjs → EndpointGenerator-npWEDoK2.cjs} +77 -7
- package/dist/EndpointGenerator-npWEDoK2.cjs.map +1 -0
- package/dist/FunctionGenerator-429-9NER.d.cts +14 -0
- package/dist/FunctionGenerator-BQ4ehoID.d.mts +14 -0
- package/dist/{FunctionGenerator-BNE_GC7N.mjs → FunctionGenerator-CVk0h8tO.mjs} +2 -2
- package/dist/{FunctionGenerator-BNE_GC7N.mjs.map → FunctionGenerator-CVk0h8tO.mjs.map} +1 -1
- package/dist/{FunctionGenerator-FgZUTd8L.cjs → FunctionGenerator-DYTnyr4c.cjs} +2 -2
- package/dist/{FunctionGenerator-FgZUTd8L.cjs.map → FunctionGenerator-DYTnyr4c.cjs.map} +1 -1
- package/dist/Generator-BjHK_qce.d.mts +27 -0
- package/dist/{Generator-UanJW0_V.mjs → Generator-CDt4pB3W.mjs} +1 -1
- package/dist/{Generator-UanJW0_V.mjs.map → Generator-CDt4pB3W.mjs.map} +1 -1
- package/dist/{Generator-CDoEXCDg.cjs → Generator-CLVplqm2.cjs} +1 -1
- package/dist/{Generator-CDoEXCDg.cjs.map → Generator-CLVplqm2.cjs.map} +1 -1
- package/dist/Generator-DxQMCQp7.d.cts +27 -0
- package/dist/OpenApiTsGenerator-Be-sKGTT.cjs +501 -0
- package/dist/OpenApiTsGenerator-Be-sKGTT.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-C4mHHaku.mjs +495 -0
- package/dist/OpenApiTsGenerator-C4mHHaku.mjs.map +1 -0
- package/dist/SubscriberGenerator-7uX42xyG.d.mts +15 -0
- package/dist/{SubscriberGenerator-Dnlj_1FK.mjs → SubscriberGenerator-DABaJXML.mjs} +2 -2
- package/dist/{SubscriberGenerator-Dnlj_1FK.mjs.map → SubscriberGenerator-DABaJXML.mjs.map} +1 -1
- package/dist/{SubscriberGenerator-Bd-a7aiw.cjs → SubscriberGenerator-D_zpNGFr.cjs} +2 -2
- package/dist/{SubscriberGenerator-Bd-a7aiw.cjs.map → SubscriberGenerator-D_zpNGFr.cjs.map} +1 -1
- package/dist/SubscriberGenerator-Dtb3HS4i.d.cts +15 -0
- package/dist/api-B3SCEHPf.cjs +190 -0
- package/dist/api-B3SCEHPf.cjs.map +1 -0
- package/dist/api-BKIN0s0S.mjs +184 -0
- package/dist/api-BKIN0s0S.mjs.map +1 -0
- package/dist/build/index.cjs +11 -10
- package/dist/build/index.d.cts +7 -0
- package/dist/build/index.d.mts +7 -0
- package/dist/build/index.mjs +11 -10
- package/dist/build/manifests.cjs +1 -1
- package/dist/build/manifests.d.cts +13 -0
- package/dist/build/manifests.d.mts +13 -0
- package/dist/build/manifests.mjs +1 -1
- package/dist/build/providerResolver.cjs +1 -1
- package/dist/build/providerResolver.d.cts +23 -0
- package/dist/build/providerResolver.d.mts +23 -0
- package/dist/build/providerResolver.mjs +1 -1
- package/dist/build/types.d.cts +3 -0
- package/dist/build/types.d.mts +3 -0
- package/dist/{build-C6uEGRj8.mjs → build-B8C_qHir.mjs} +15 -13
- package/dist/build-B8C_qHir.mjs.map +1 -0
- package/dist/{build-CBYBPZpC.cjs → build-D0Wr49bf.cjs} +15 -13
- package/dist/build-D0Wr49bf.cjs.map +1 -0
- package/dist/config-Ba-Gbpbc.d.cts +11 -0
- package/dist/config-Bq72aj8e.mjs +75 -0
- package/dist/config-Bq72aj8e.mjs.map +1 -0
- package/dist/config-CFls09Ey.cjs +93 -0
- package/dist/config-CFls09Ey.cjs.map +1 -0
- package/dist/config-CLEDqKO3.cjs +157 -0
- package/dist/config-CLEDqKO3.cjs.map +1 -0
- package/dist/config-DBsmMDhf.d.mts +11 -0
- package/dist/config-Dp8RonV_.mjs +151 -0
- package/dist/config-Dp8RonV_.mjs.map +1 -0
- package/dist/config.cjs +4 -2
- package/dist/config.d.cts +48 -0
- package/dist/config.d.mts +48 -0
- package/dist/config.mjs +2 -2
- package/dist/dev/index.cjs +12 -10
- package/dist/dev/index.d.cts +36 -0
- package/dist/dev/index.d.mts +36 -0
- package/dist/dev/index.mjs +10 -10
- package/dist/dev-B734w3L1.mjs +343 -0
- package/dist/dev-B734w3L1.mjs.map +1 -0
- package/dist/{dev-DbtyToc7.cjs → dev-DHqYn8k4.cjs} +161 -47
- package/dist/dev-DHqYn8k4.cjs.map +1 -0
- package/dist/docker-5d8Yh5_X.cjs +119 -0
- package/dist/docker-5d8Yh5_X.cjs.map +1 -0
- package/dist/docker-DlUqdFle.mjs +113 -0
- package/dist/docker-DlUqdFle.mjs.map +1 -0
- package/dist/env-B-OKjgI4.cjs +144 -0
- package/dist/env-B-OKjgI4.cjs.map +1 -0
- package/dist/env-HfuJRlg5.d.cts +11 -0
- package/dist/env-nd-iQPYM.d.mts +11 -0
- package/dist/env-tv1HlZlw.mjs +138 -0
- package/dist/env-tv1HlZlw.mjs.map +1 -0
- package/dist/generators/CronGenerator.cjs +2 -2
- package/dist/generators/CronGenerator.d.cts +5 -0
- package/dist/generators/CronGenerator.d.mts +5 -0
- package/dist/generators/CronGenerator.mjs +2 -2
- package/dist/generators/EndpointGenerator.cjs +2 -2
- package/dist/generators/EndpointGenerator.d.cts +5 -0
- package/dist/generators/EndpointGenerator.d.mts +5 -0
- package/dist/generators/EndpointGenerator.mjs +2 -2
- package/dist/generators/FunctionGenerator.cjs +2 -2
- package/dist/generators/FunctionGenerator.d.cts +5 -0
- package/dist/generators/FunctionGenerator.d.mts +5 -0
- package/dist/generators/FunctionGenerator.mjs +2 -2
- package/dist/generators/Generator.cjs +1 -1
- package/dist/generators/Generator.d.cts +4 -0
- package/dist/generators/Generator.d.mts +4 -0
- package/dist/generators/Generator.mjs +1 -1
- package/dist/generators/OpenApiTsGenerator.cjs +3 -0
- package/dist/generators/OpenApiTsGenerator.d.cts +44 -0
- package/dist/generators/OpenApiTsGenerator.d.mts +44 -0
- package/dist/generators/OpenApiTsGenerator.mjs +3 -0
- package/dist/generators/SubscriberGenerator.cjs +2 -2
- package/dist/generators/SubscriberGenerator.d.cts +5 -0
- package/dist/generators/SubscriberGenerator.d.mts +5 -0
- package/dist/generators/SubscriberGenerator.mjs +2 -2
- package/dist/generators/index.cjs +6 -6
- package/dist/generators/index.d.cts +8 -0
- package/dist/generators/index.d.mts +8 -0
- package/dist/generators/index.mjs +6 -6
- package/dist/index-C523No_B.d.mts +64 -0
- package/dist/index-DrzN4xkQ.d.cts +64 -0
- package/dist/index.cjs +56 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +56 -18
- package/dist/index.mjs.map +1 -1
- package/dist/init/generators/config.cjs +3 -0
- package/dist/init/generators/config.d.cts +3 -0
- package/dist/init/generators/config.d.mts +3 -0
- package/dist/init/generators/config.mjs +3 -0
- package/dist/init/generators/docker.cjs +3 -0
- package/dist/init/generators/docker.d.cts +11 -0
- package/dist/init/generators/docker.d.mts +11 -0
- package/dist/init/generators/docker.mjs +3 -0
- package/dist/init/generators/env.cjs +3 -0
- package/dist/init/generators/env.d.cts +3 -0
- package/dist/init/generators/env.d.mts +3 -0
- package/dist/init/generators/env.mjs +3 -0
- package/dist/init/generators/index.cjs +9 -0
- package/dist/init/generators/index.d.cts +6 -0
- package/dist/init/generators/index.d.mts +6 -0
- package/dist/init/generators/index.mjs +6 -0
- package/dist/init/generators/models.cjs +3 -0
- package/dist/init/generators/models.d.cts +11 -0
- package/dist/init/generators/models.d.mts +11 -0
- package/dist/init/generators/models.mjs +3 -0
- package/dist/init/generators/monorepo.cjs +3 -0
- package/dist/init/generators/monorepo.d.cts +11 -0
- package/dist/init/generators/monorepo.d.mts +11 -0
- package/dist/init/generators/monorepo.mjs +3 -0
- package/dist/init/generators/package.cjs +3 -0
- package/dist/init/generators/package.d.cts +3 -0
- package/dist/init/generators/package.d.mts +3 -0
- package/dist/init/generators/package.mjs +3 -0
- package/dist/init/generators/source.cjs +3 -0
- package/dist/init/generators/source.d.cts +3 -0
- package/dist/init/generators/source.d.mts +3 -0
- package/dist/init/generators/source.mjs +3 -0
- package/dist/init/index.cjs +16 -0
- package/dist/init/index.d.cts +17 -0
- package/dist/init/index.d.mts +17 -0
- package/dist/init/index.mjs +16 -0
- package/dist/init/templates/api.cjs +3 -0
- package/dist/init/templates/api.d.cts +7 -0
- package/dist/init/templates/api.d.mts +7 -0
- package/dist/init/templates/api.mjs +3 -0
- package/dist/init/templates/index.cjs +10 -0
- package/dist/init/templates/index.d.cts +2 -0
- package/dist/init/templates/index.d.mts +2 -0
- package/dist/init/templates/index.mjs +7 -0
- package/dist/init/templates/minimal.cjs +3 -0
- package/dist/init/templates/minimal.d.cts +7 -0
- package/dist/init/templates/minimal.d.mts +7 -0
- package/dist/init/templates/minimal.mjs +3 -0
- package/dist/init/templates/serverless.cjs +3 -0
- package/dist/init/templates/serverless.d.cts +7 -0
- package/dist/init/templates/serverless.d.mts +7 -0
- package/dist/init/templates/serverless.mjs +3 -0
- package/dist/init/templates/worker.cjs +3 -0
- package/dist/init/templates/worker.d.cts +7 -0
- package/dist/init/templates/worker.d.mts +7 -0
- package/dist/init/templates/worker.mjs +3 -0
- package/dist/init/utils.cjs +7 -0
- package/dist/init/utils.d.cts +25 -0
- package/dist/init/utils.d.mts +25 -0
- package/dist/init/utils.mjs +3 -0
- package/dist/init-CtOnZn3G.mjs +145 -0
- package/dist/init-CtOnZn3G.mjs.map +1 -0
- package/dist/init-qLFsWR-R.cjs +151 -0
- package/dist/init-qLFsWR-R.cjs.map +1 -0
- package/dist/{manifests-C2eMoMUm.mjs → manifests-DIA_2QYd.mjs} +1 -1
- package/dist/{manifests-C2eMoMUm.mjs.map → manifests-DIA_2QYd.mjs.map} +1 -1
- package/dist/{manifests-CK1VV_pM.cjs → manifests-VJ9-2JpW.cjs} +1 -1
- package/dist/{manifests-CK1VV_pM.cjs.map → manifests-VJ9-2JpW.cjs.map} +1 -1
- package/dist/minimal-Bdhhpp7v.cjs +93 -0
- package/dist/minimal-Bdhhpp7v.cjs.map +1 -0
- package/dist/minimal-C4GsE45s.mjs +87 -0
- package/dist/minimal-C4GsE45s.mjs.map +1 -0
- package/dist/models-DyNwdOcz.cjs +121 -0
- package/dist/models-DyNwdOcz.cjs.map +1 -0
- package/dist/models-cvNg6Oea.mjs +115 -0
- package/dist/models-cvNg6Oea.mjs.map +1 -0
- package/dist/monorepo-Cknwzj5C.mjs +184 -0
- package/dist/monorepo-Cknwzj5C.mjs.map +1 -0
- package/dist/monorepo-sEK8gW59.cjs +190 -0
- package/dist/monorepo-sEK8gW59.cjs.map +1 -0
- package/dist/openapi-BQWPWyNB.cjs +56 -0
- package/dist/openapi-BQWPWyNB.cjs.map +1 -0
- package/dist/openapi-DBX8cJJ8.mjs +50 -0
- package/dist/openapi-DBX8cJJ8.mjs.map +1 -0
- package/dist/{openapi-react-query-D9Z7lh0p.cjs → openapi-react-query-DxHjXQvg.cjs} +1 -1
- package/dist/{openapi-react-query-D9Z7lh0p.cjs.map → openapi-react-query-DxHjXQvg.cjs.map} +1 -1
- package/dist/{openapi-react-query-MEBlYIM1.mjs → openapi-react-query-o7Mp1Jd5.mjs} +1 -1
- package/dist/{openapi-react-query-MEBlYIM1.mjs.map → openapi-react-query-o7Mp1Jd5.mjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts +11 -0
- package/dist/openapi-react-query.d.mts +11 -0
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +5 -4
- package/dist/openapi.d.cts +11 -0
- package/dist/openapi.d.mts +11 -0
- package/dist/openapi.mjs +5 -4
- package/dist/package-C7WhWU8m.d.mts +11 -0
- package/dist/package-CIfmeuSW.mjs +51 -0
- package/dist/package-CIfmeuSW.mjs.map +1 -0
- package/dist/package-DvWEMz6z.d.cts +11 -0
- package/dist/package-PP-o1nvq.cjs +57 -0
- package/dist/package-PP-o1nvq.cjs.map +1 -0
- package/dist/{providerResolver-B_TjNF0_.mjs → providerResolver-DEVKngbC.mjs} +1 -1
- package/dist/{providerResolver-B_TjNF0_.mjs.map → providerResolver-DEVKngbC.mjs.map} +1 -1
- package/dist/{providerResolver-DgvzNfP4.cjs → providerResolver-DOTbN9jo.cjs} +1 -1
- package/dist/{providerResolver-DgvzNfP4.cjs.map → providerResolver-DOTbN9jo.cjs.map} +1 -1
- package/dist/serverless-DkHBF2vC.mjs +108 -0
- package/dist/serverless-DkHBF2vC.mjs.map +1 -0
- package/dist/serverless-Yav3GRVz.cjs +114 -0
- package/dist/serverless-Yav3GRVz.cjs.map +1 -0
- package/dist/source-D6v2BnKT.d.mts +11 -0
- package/dist/source-D8fK9qRo.d.cts +11 -0
- package/dist/source-DT5Xhiob.cjs +17 -0
- package/dist/source-DT5Xhiob.cjs.map +1 -0
- package/dist/source-DnaH_MLA.mjs +11 -0
- package/dist/source-DnaH_MLA.mjs.map +1 -0
- package/dist/templates-CBFUwpBy.mjs +64 -0
- package/dist/templates-CBFUwpBy.mjs.map +1 -0
- package/dist/templates-DM_rtYYW.cjs +87 -0
- package/dist/templates-DM_rtYYW.cjs.map +1 -0
- package/dist/types-C4KITv-y.d.mts +51 -0
- package/dist/types-Cxl8-uwV.d.mts +129 -0
- package/dist/types-DB99_qIy.d.cts +129 -0
- package/dist/types-DLFN49M3.d.cts +51 -0
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/utils-BX3F4fT8.cjs +99 -0
- package/dist/utils-BX3F4fT8.cjs.map +1 -0
- package/dist/utils-C31-SWHP.mjs +69 -0
- package/dist/utils-C31-SWHP.mjs.map +1 -0
- package/dist/worker--8O5a3Hv.cjs +150 -0
- package/dist/worker--8O5a3Hv.cjs.map +1 -0
- package/dist/worker-Jme7uOOJ.mjs +144 -0
- package/dist/worker-Jme7uOOJ.mjs.map +1 -0
- package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +408 -0
- package/package.json +19 -4
- package/src/__tests__/loadEnvFiles.spec.ts +131 -0
- package/src/__tests__/openapi.spec.ts +78 -63
- package/src/build/index.ts +14 -16
- package/src/build/types.ts +18 -2
- package/src/config.ts +61 -2
- package/src/dev/__tests__/index.spec.ts +98 -1
- package/src/dev/index.ts +229 -42
- package/src/generators/EndpointGenerator.ts +98 -5
- package/src/generators/OpenApiTsGenerator.ts +798 -0
- package/src/index.ts +32 -3
- package/src/init/__tests__/generators.spec.ts +366 -0
- package/src/init/__tests__/init.spec.ts +341 -0
- package/src/init/__tests__/utils.spec.ts +104 -0
- package/src/init/generators/config.ts +192 -0
- package/src/init/generators/docker.ts +134 -0
- package/src/init/generators/env.ts +182 -0
- package/src/init/generators/index.ts +4 -0
- package/src/init/generators/models.ts +129 -0
- package/src/init/generators/monorepo.ts +211 -0
- package/src/init/generators/package.ts +81 -0
- package/src/init/generators/source.ts +15 -0
- package/src/init/index.ts +206 -0
- package/src/init/templates/api.ts +218 -0
- package/src/init/templates/index.ts +108 -0
- package/src/init/templates/minimal.ts +102 -0
- package/src/init/templates/serverless.ts +129 -0
- package/src/init/templates/worker.ts +169 -0
- package/src/init/utils.ts +98 -0
- package/src/openapi.ts +36 -15
- package/src/types.ts +43 -0
- package/tsdown.config.ts +1 -1
- package/dist/EndpointGenerator-C73wNoih.cjs.map +0 -1
- package/dist/EndpointGenerator-CWh18d92.mjs.map +0 -1
- package/dist/build-C6uEGRj8.mjs.map +0 -1
- package/dist/build-CBYBPZpC.cjs.map +0 -1
- package/dist/config-D1EpSGk6.cjs +0 -36
- package/dist/config-D1EpSGk6.cjs.map +0 -1
- package/dist/config-U-mdW-7Y.mjs +0 -30
- package/dist/config-U-mdW-7Y.mjs.map +0 -1
- package/dist/dev-DbtyToc7.cjs.map +0 -1
- package/dist/dev-DnGYXuMn.mjs +0 -241
- package/dist/dev-DnGYXuMn.mjs.map +0 -1
- package/dist/openapi-BTHbPrxS.mjs +0 -36
- package/dist/openapi-BTHbPrxS.mjs.map +0 -1
- package/dist/openapi-CewcfoRH.cjs +0 -42
- package/dist/openapi-CewcfoRH.cjs.map +0 -1
- /package/dist/{generators-CEKtVh81.cjs → generators-3IemvCLk.cjs} +0 -0
- /package/dist/{generators-CsLujGXs.mjs → generators-FNpdfN6J.mjs} +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# OpenAPI TypeScript Generation - Design Document
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document describes the design for generating TypeScript OpenAPI specifications with integrated authentication support. The feature extends `gkm openapi` with a `--ts` flag that outputs a TypeScript module instead of JSON, enabling:
|
|
6
|
+
|
|
7
|
+
1. **Type-safe paths** - Full TypeScript interface for API routes
|
|
8
|
+
2. **Runtime auth map** - Per-endpoint authentication requirements
|
|
9
|
+
3. **Reusable schemas** - TypeScript interfaces extracted from Zod/Valibot schemas
|
|
10
|
+
4. **Security scheme definitions** - OpenAPI security schemes as typed constants
|
|
11
|
+
|
|
12
|
+
## Motivation
|
|
13
|
+
|
|
14
|
+
### Current Flow (JSON-based)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Endpoints → gkm openapi → openapi.json → openapi-typescript → types.d.ts
|
|
18
|
+
↓
|
|
19
|
+
(no auth info at runtime)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Problems:**
|
|
23
|
+
- Auth requirements lost at runtime - OpenAPI security info exists but isn't usable by the fetcher
|
|
24
|
+
- Two-step generation - need external tool (`openapi-typescript`) for types
|
|
25
|
+
- No schema reuse - generated types are isolated, can't reference shared interfaces
|
|
26
|
+
|
|
27
|
+
### Proposed Flow (TypeScript-native)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Endpoints → gkm openapi --ts → openapi.ts
|
|
31
|
+
↓
|
|
32
|
+
Exports:
|
|
33
|
+
├── paths (type)
|
|
34
|
+
├── endpointAuth (runtime map)
|
|
35
|
+
├── securitySchemes (runtime definitions)
|
|
36
|
+
└── schema interfaces (reusable types)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Command Interface
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Default: generates TypeScript
|
|
43
|
+
gkm openapi --output ./src/api/openapi.ts
|
|
44
|
+
|
|
45
|
+
# Explicit JSON output (legacy)
|
|
46
|
+
gkm openapi --json --output ./openapi.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Options
|
|
50
|
+
|
|
51
|
+
| Flag | Description | Default |
|
|
52
|
+
|------|-------------|---------|
|
|
53
|
+
| `--json` | Generate JSON instead of TypeScript (legacy) | `false` |
|
|
54
|
+
| `--output` | Output file path | `openapi.ts` |
|
|
55
|
+
|
|
56
|
+
## Generated Output Structure
|
|
57
|
+
|
|
58
|
+
### File: `openapi.ts`
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Auto-generated by @geekmidas/cli - DO NOT EDIT
|
|
62
|
+
import type { OpenAPIV3_1 } from 'openapi-types';
|
|
63
|
+
|
|
64
|
+
// ============================================================
|
|
65
|
+
// Security Schemes
|
|
66
|
+
// ============================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Available security schemes for this API.
|
|
70
|
+
* Maps authorizer names to OpenAPI security scheme definitions.
|
|
71
|
+
*/
|
|
72
|
+
export const securitySchemes = {
|
|
73
|
+
bearer: {
|
|
74
|
+
type: 'http',
|
|
75
|
+
scheme: 'bearer',
|
|
76
|
+
bearerFormat: 'JWT',
|
|
77
|
+
},
|
|
78
|
+
iam: {
|
|
79
|
+
type: 'apiKey',
|
|
80
|
+
in: 'header',
|
|
81
|
+
name: 'Authorization',
|
|
82
|
+
'x-amazon-apigateway-authtype': 'awsSigv4',
|
|
83
|
+
},
|
|
84
|
+
apiKey: {
|
|
85
|
+
type: 'apiKey',
|
|
86
|
+
in: 'header',
|
|
87
|
+
name: 'X-API-Key',
|
|
88
|
+
},
|
|
89
|
+
} as const satisfies Record<string, OpenAPIV3_1.SecuritySchemeObject>;
|
|
90
|
+
|
|
91
|
+
export type SecuritySchemeId = keyof typeof securitySchemes;
|
|
92
|
+
|
|
93
|
+
// ============================================================
|
|
94
|
+
// Endpoint Authentication Map
|
|
95
|
+
// ============================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Runtime map of endpoints to their required authentication scheme.
|
|
99
|
+
* `null` indicates a public endpoint (no auth required).
|
|
100
|
+
*/
|
|
101
|
+
export const endpointAuth = {
|
|
102
|
+
'POST /tenants': 'iam',
|
|
103
|
+
'GET /tenants/{id}': 'bearer',
|
|
104
|
+
'PUT /tenants/{id}': 'bearer',
|
|
105
|
+
'DELETE /tenants/{id}': 'bearer',
|
|
106
|
+
'GET /health': null,
|
|
107
|
+
'GET /docs': null,
|
|
108
|
+
} as const satisfies Record<string, SecuritySchemeId | null>;
|
|
109
|
+
|
|
110
|
+
export type AuthenticatedEndpoint = {
|
|
111
|
+
[K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? never : K;
|
|
112
|
+
}[keyof typeof endpointAuth];
|
|
113
|
+
|
|
114
|
+
export type PublicEndpoint = {
|
|
115
|
+
[K in keyof typeof endpointAuth]: typeof endpointAuth[K] extends null ? K : never;
|
|
116
|
+
}[keyof typeof endpointAuth];
|
|
117
|
+
|
|
118
|
+
// ============================================================
|
|
119
|
+
// Schema Definitions (Reusable Types)
|
|
120
|
+
// ============================================================
|
|
121
|
+
|
|
122
|
+
export interface Tenant {
|
|
123
|
+
id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
createdAt: string;
|
|
126
|
+
updatedAt: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface CreateTenantInput {
|
|
130
|
+
name: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface UpdateTenantInput {
|
|
134
|
+
name?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================
|
|
138
|
+
// OpenAPI Paths
|
|
139
|
+
// ============================================================
|
|
140
|
+
|
|
141
|
+
export interface paths {
|
|
142
|
+
'/tenants': {
|
|
143
|
+
post: {
|
|
144
|
+
requestBody: {
|
|
145
|
+
content: {
|
|
146
|
+
'application/json': CreateTenantInput;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
responses: {
|
|
150
|
+
201: {
|
|
151
|
+
content: {
|
|
152
|
+
'application/json': Tenant;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
'/tenants/{id}': {
|
|
159
|
+
parameters: {
|
|
160
|
+
path: {
|
|
161
|
+
id: string;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
get: {
|
|
165
|
+
responses: {
|
|
166
|
+
200: {
|
|
167
|
+
content: {
|
|
168
|
+
'application/json': Tenant;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
put: {
|
|
174
|
+
requestBody: {
|
|
175
|
+
content: {
|
|
176
|
+
'application/json': UpdateTenantInput;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
responses: {
|
|
180
|
+
200: {
|
|
181
|
+
content: {
|
|
182
|
+
'application/json': Tenant;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
delete: {
|
|
188
|
+
responses: {
|
|
189
|
+
204: {
|
|
190
|
+
content: never;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
'/health': {
|
|
196
|
+
get: {
|
|
197
|
+
responses: {
|
|
198
|
+
200: {
|
|
199
|
+
content: {
|
|
200
|
+
'application/json': { status: string };
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================
|
|
209
|
+
// Full OpenAPI Specification (Optional)
|
|
210
|
+
// ============================================================
|
|
211
|
+
|
|
212
|
+
export const spec = {
|
|
213
|
+
openapi: '3.1.0',
|
|
214
|
+
info: {
|
|
215
|
+
title: 'Tenant API',
|
|
216
|
+
version: '1.0.0',
|
|
217
|
+
},
|
|
218
|
+
paths: { /* ... */ },
|
|
219
|
+
components: {
|
|
220
|
+
securitySchemes,
|
|
221
|
+
schemas: { /* ... */ },
|
|
222
|
+
},
|
|
223
|
+
} as const satisfies OpenAPIV3_1.Document;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Implementation Details
|
|
227
|
+
|
|
228
|
+
### 1. Authorizer to Security Scheme Mapping
|
|
229
|
+
|
|
230
|
+
The `Authorizer.type` field maps to OpenAPI security schemes:
|
|
231
|
+
|
|
232
|
+
| Authorizer Type | OpenAPI Security Scheme |
|
|
233
|
+
|-----------------|------------------------|
|
|
234
|
+
| `jwt`, `bearer` | `{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }` |
|
|
235
|
+
| `iam`, `aws-sigv4` | `{ type: 'apiKey', in: 'header', name: 'Authorization', 'x-amazon-apigateway-authtype': 'awsSigv4' }` |
|
|
236
|
+
| `apiKey` | `{ type: 'apiKey', in: metadata.in, name: metadata.name }` |
|
|
237
|
+
| `oauth2` | `{ type: 'oauth2', flows: { ... } }` |
|
|
238
|
+
| `oidc` | `{ type: 'openIdConnect', openIdConnectUrl: metadata.issuer }` |
|
|
239
|
+
| `none` / undefined | `null` (public endpoint) |
|
|
240
|
+
|
|
241
|
+
### 2. Schema Extraction
|
|
242
|
+
|
|
243
|
+
Schemas are extracted from StandardSchema (Zod/Valibot) definitions:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// From endpoint definition
|
|
247
|
+
const endpoint = e
|
|
248
|
+
.post('/tenants')
|
|
249
|
+
.body(z.object({ name: z.string() }))
|
|
250
|
+
.output(z.object({ id: z.string(), name: z.string() }))
|
|
251
|
+
.handle(async ({ body }) => { ... });
|
|
252
|
+
|
|
253
|
+
// Extracted as
|
|
254
|
+
export interface CreateTenantInput {
|
|
255
|
+
name: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface CreateTenantOutput {
|
|
259
|
+
id: string;
|
|
260
|
+
name: string;
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 3. Naming Strategy
|
|
265
|
+
|
|
266
|
+
| Schema Location | Generated Name |
|
|
267
|
+
|-----------------|----------------|
|
|
268
|
+
| Body schema | `{OperationId}Input` or `{Method}{Route}Input` |
|
|
269
|
+
| Output schema | `{OperationId}Output` or `{Method}{Route}Output` |
|
|
270
|
+
| Params schema | `{OperationId}Params` |
|
|
271
|
+
| Query schema | `{OperationId}Query` |
|
|
272
|
+
|
|
273
|
+
### 4. Component Collector Enhancement
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// packages/schema/src/openapi.ts
|
|
277
|
+
export interface ComponentCollector {
|
|
278
|
+
schemas: Record<string, OpenAPIV3_1.SchemaObject>;
|
|
279
|
+
securitySchemes: Record<string, OpenAPIV3_1.SecuritySchemeObject>;
|
|
280
|
+
addSchema(id: string, schema: OpenAPIV3_1.SchemaObject): void;
|
|
281
|
+
addSecurityScheme(id: string, scheme: OpenAPIV3_1.SecuritySchemeObject): void;
|
|
282
|
+
getReference(id: string): OpenAPIV3_1.ReferenceObject;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Integration with Auth-Aware Fetcher
|
|
287
|
+
|
|
288
|
+
The generated `endpointAuth` map enables automatic auth handling:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// packages/client/src/auth-fetcher.ts
|
|
292
|
+
import { endpointAuth, securitySchemes, type paths } from './openapi';
|
|
293
|
+
import { TokenClient } from '@geekmidas/auth/client';
|
|
294
|
+
|
|
295
|
+
export function createAuthAwareFetcher<Paths>(options: AuthFetcherOptions) {
|
|
296
|
+
const { tokenClient, awsSigner, apiKeyProvider } = options;
|
|
297
|
+
|
|
298
|
+
return async <T extends TypedEndpoint<Paths>>(
|
|
299
|
+
endpoint: T,
|
|
300
|
+
config?: FilteredRequestConfig<Paths, T>,
|
|
301
|
+
) => {
|
|
302
|
+
const authScheme = endpointAuth[endpoint as keyof typeof endpointAuth];
|
|
303
|
+
let headers: Record<string, string> = {};
|
|
304
|
+
|
|
305
|
+
if (authScheme) {
|
|
306
|
+
const scheme = securitySchemes[authScheme];
|
|
307
|
+
|
|
308
|
+
switch (scheme.type) {
|
|
309
|
+
case 'http':
|
|
310
|
+
if (scheme.scheme === 'bearer') {
|
|
311
|
+
headers = await tokenClient.createValidAuthHeaders();
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case 'apiKey':
|
|
315
|
+
if (scheme['x-amazon-apigateway-authtype'] === 'awsSigv4') {
|
|
316
|
+
headers = await awsSigner.sign(endpoint, config);
|
|
317
|
+
} else {
|
|
318
|
+
headers[scheme.name] = await apiKeyProvider.getKey();
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return baseFetcher.request(endpoint, {
|
|
325
|
+
...config,
|
|
326
|
+
headers: { ...headers, ...config?.headers },
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## File Structure Changes
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
packages/
|
|
336
|
+
├── cli/
|
|
337
|
+
│ └── src/
|
|
338
|
+
│ ├── openapi.ts # Updated: add --ts support
|
|
339
|
+
│ └── generators/
|
|
340
|
+
│ └── OpenApiTsGenerator.ts # New: TypeScript generation
|
|
341
|
+
├── schema/
|
|
342
|
+
│ └── src/
|
|
343
|
+
│ └── openapi.ts # Updated: security scheme support
|
|
344
|
+
└── client/
|
|
345
|
+
└── src/
|
|
346
|
+
└── auth-fetcher.ts # New: auth-aware fetcher
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Migration Path
|
|
350
|
+
|
|
351
|
+
### Breaking Change
|
|
352
|
+
|
|
353
|
+
TypeScript is now the default output format. Existing JSON users must add `--json` flag.
|
|
354
|
+
|
|
355
|
+
### Existing JSON Users
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Before
|
|
359
|
+
gkm openapi --output ./openapi.json
|
|
360
|
+
|
|
361
|
+
# After (explicit JSON)
|
|
362
|
+
gkm openapi --json --output ./openapi.json
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Adopting TypeScript Output (New Default)
|
|
366
|
+
|
|
367
|
+
1. Run `gkm openapi --output ./src/api/openapi.ts`
|
|
368
|
+
2. Update imports from `./openapi-types` to `./openapi`
|
|
369
|
+
3. Use `createAuthAwareFetcher` instead of `createTypedFetcher`
|
|
370
|
+
|
|
371
|
+
## Testing Strategy
|
|
372
|
+
|
|
373
|
+
1. **Unit Tests**
|
|
374
|
+
- Authorizer → security scheme mapping
|
|
375
|
+
- Schema extraction from StandardSchema
|
|
376
|
+
- TypeScript code generation
|
|
377
|
+
|
|
378
|
+
2. **Integration Tests**
|
|
379
|
+
- Full endpoint → TypeScript output pipeline
|
|
380
|
+
- Generated code compiles without errors
|
|
381
|
+
- Auth map matches endpoint authorizers
|
|
382
|
+
|
|
383
|
+
3. **E2E Tests**
|
|
384
|
+
- Auth-aware fetcher selects correct auth per endpoint
|
|
385
|
+
- Bearer endpoints get JWT headers
|
|
386
|
+
- IAM endpoints get SigV4 signatures
|
|
387
|
+
- Public endpoints get no auth headers
|
|
388
|
+
|
|
389
|
+
## Open Questions
|
|
390
|
+
|
|
391
|
+
1. **Schema naming conflicts** - What if two endpoints have the same operationId?
|
|
392
|
+
- Proposal: Append route hash suffix if conflict detected
|
|
393
|
+
|
|
394
|
+
2. **Circular schema references** - How to handle `User { friends: User[] }`?
|
|
395
|
+
- Proposal: Use TypeScript `interface` declarations which support self-reference
|
|
396
|
+
|
|
397
|
+
3. **Optional vs required auth** - Some endpoints allow optional auth (return more data if authenticated)
|
|
398
|
+
- Proposal: Add `optional` auth type: `'bearer' | 'bearer?' | null`
|
|
399
|
+
|
|
400
|
+
## Timeline
|
|
401
|
+
|
|
402
|
+
| Phase | Description | Estimate |
|
|
403
|
+
|-------|-------------|----------|
|
|
404
|
+
| 1 | Schema package updates (ComponentCollector) | - |
|
|
405
|
+
| 2 | CLI TypeScript generator | - |
|
|
406
|
+
| 3 | Client auth-aware fetcher | - |
|
|
407
|
+
| 4 | Documentation and examples | - |
|
|
408
|
+
| 5 | Testing and refinement | - |
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
4
5
|
"private": false,
|
|
5
6
|
"type": "module",
|
|
6
7
|
"exports": {
|
|
@@ -9,6 +10,11 @@
|
|
|
9
10
|
"import": "./dist/index.mjs",
|
|
10
11
|
"require": "./dist/index.cjs"
|
|
11
12
|
},
|
|
13
|
+
"./config": {
|
|
14
|
+
"types": "./dist/config.d.ts",
|
|
15
|
+
"import": "./dist/config.mjs",
|
|
16
|
+
"require": "./dist/config.cjs"
|
|
17
|
+
},
|
|
12
18
|
"./openapi": {
|
|
13
19
|
"types": "./dist/openapi.d.ts",
|
|
14
20
|
"import": "./dist/openapi.mjs",
|
|
@@ -31,24 +37,33 @@
|
|
|
31
37
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
32
38
|
"chokidar": "~4.0.3",
|
|
33
39
|
"commander": "^12.1.0",
|
|
40
|
+
"dotenv": "~17.2.3",
|
|
34
41
|
"fast-glob": "^3.3.2",
|
|
35
42
|
"lodash.kebabcase": "^4.1.1",
|
|
36
|
-
"openapi-typescript": "^7.4.2"
|
|
43
|
+
"openapi-typescript": "^7.4.2",
|
|
44
|
+
"prompts": "~2.4.2"
|
|
37
45
|
},
|
|
38
46
|
"devDependencies": {
|
|
39
47
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
40
48
|
"@types/node": "~24.9.1",
|
|
49
|
+
"@types/prompts": "~2.4.9",
|
|
41
50
|
"typescript": "^5.8.2",
|
|
42
51
|
"vitest": "^3.2.4",
|
|
43
52
|
"zod": "~4.1.13",
|
|
44
53
|
"@geekmidas/testkit": "0.0.17"
|
|
45
54
|
},
|
|
46
55
|
"peerDependencies": {
|
|
47
|
-
"@geekmidas/constructs": "~0.0
|
|
48
|
-
"@geekmidas/envkit": "~0.0
|
|
56
|
+
"@geekmidas/constructs": "~0.2.0",
|
|
57
|
+
"@geekmidas/envkit": "~0.1.0",
|
|
49
58
|
"@geekmidas/logger": "~0.0.1",
|
|
59
|
+
"@geekmidas/telescope": "~0.0.1",
|
|
50
60
|
"@geekmidas/schema": "~0.0.2"
|
|
51
61
|
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@geekmidas/telescope": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
52
67
|
"scripts": {
|
|
53
68
|
"ts": "tsc --noEmit --skipLibCheck src/**/*.ts",
|
|
54
69
|
"test": "vitest",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { loadEnvFiles } from '../dev';
|
|
6
|
+
|
|
7
|
+
describe('loadEnvFiles', () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
const originalEnv = { ...process.env };
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Create a temporary test directory
|
|
13
|
+
testDir = join(tmpdir(), `gkm-test-${Date.now()}`);
|
|
14
|
+
mkdirSync(testDir, { recursive: true });
|
|
15
|
+
|
|
16
|
+
// Reset process.env to original state
|
|
17
|
+
process.env = { ...originalEnv };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
// Clean up test directory
|
|
22
|
+
if (existsSync(testDir)) {
|
|
23
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Reset process.env
|
|
27
|
+
process.env = originalEnv;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should load a single .env file', () => {
|
|
31
|
+
writeFileSync(join(testDir, '.env'), 'TEST_VAR=hello\nANOTHER_VAR=world');
|
|
32
|
+
|
|
33
|
+
const { loaded, missing } = loadEnvFiles('.env', testDir);
|
|
34
|
+
|
|
35
|
+
expect(loaded).toEqual(['.env']);
|
|
36
|
+
expect(missing).toEqual([]);
|
|
37
|
+
expect(process.env.TEST_VAR).toBe('hello');
|
|
38
|
+
expect(process.env.ANOTHER_VAR).toBe('world');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should load multiple env files in order', () => {
|
|
42
|
+
writeFileSync(join(testDir, '.env'), 'VAR1=base\nVAR2=base');
|
|
43
|
+
writeFileSync(join(testDir, '.env.local'), 'VAR2=local\nVAR3=local');
|
|
44
|
+
|
|
45
|
+
const { loaded, missing } = loadEnvFiles(['.env', '.env.local'], testDir);
|
|
46
|
+
|
|
47
|
+
expect(loaded).toEqual(['.env', '.env.local']);
|
|
48
|
+
expect(missing).toEqual([]);
|
|
49
|
+
expect(process.env.VAR1).toBe('base');
|
|
50
|
+
expect(process.env.VAR2).toBe('local'); // Overridden by .env.local
|
|
51
|
+
expect(process.env.VAR3).toBe('local');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should report missing files when explicitly configured', () => {
|
|
55
|
+
writeFileSync(join(testDir, '.env'), 'VAR=value');
|
|
56
|
+
|
|
57
|
+
const { loaded, missing } = loadEnvFiles(
|
|
58
|
+
['.env', '.env.local', '.env.missing'],
|
|
59
|
+
testDir,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(loaded).toEqual(['.env']);
|
|
63
|
+
expect(missing).toEqual(['.env.local', '.env.missing']);
|
|
64
|
+
expect(process.env.VAR).toBe('value');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should not report missing .env when using default', () => {
|
|
68
|
+
// No .env file exists
|
|
69
|
+
const { loaded, missing } = loadEnvFiles(undefined, testDir);
|
|
70
|
+
|
|
71
|
+
expect(loaded).toEqual([]);
|
|
72
|
+
expect(missing).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle undefined config with default .env', () => {
|
|
76
|
+
writeFileSync(join(testDir, '.env'), 'DEFAULT_VAR=default');
|
|
77
|
+
|
|
78
|
+
const { loaded, missing } = loadEnvFiles(undefined, testDir);
|
|
79
|
+
|
|
80
|
+
expect(loaded).toEqual(['.env']);
|
|
81
|
+
expect(missing).toEqual([]);
|
|
82
|
+
expect(process.env.DEFAULT_VAR).toBe('default');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle string config as single file', () => {
|
|
86
|
+
writeFileSync(join(testDir, '.env.production'), 'PROD_VAR=production');
|
|
87
|
+
|
|
88
|
+
const { loaded, missing } = loadEnvFiles('.env.production', testDir);
|
|
89
|
+
|
|
90
|
+
expect(loaded).toEqual(['.env.production']);
|
|
91
|
+
expect(missing).toEqual([]);
|
|
92
|
+
expect(process.env.PROD_VAR).toBe('production');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should override earlier files with later files', () => {
|
|
96
|
+
writeFileSync(join(testDir, '.env'), 'SHARED=first\nONLY_FIRST=yes');
|
|
97
|
+
writeFileSync(join(testDir, '.env.local'), 'SHARED=second');
|
|
98
|
+
writeFileSync(join(testDir, '.env.dev'), 'SHARED=third\nONLY_DEV=yes');
|
|
99
|
+
|
|
100
|
+
const { loaded } = loadEnvFiles(
|
|
101
|
+
['.env', '.env.local', '.env.dev'],
|
|
102
|
+
testDir,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(loaded).toEqual(['.env', '.env.local', '.env.dev']);
|
|
106
|
+
expect(process.env.SHARED).toBe('third');
|
|
107
|
+
expect(process.env.ONLY_FIRST).toBe('yes');
|
|
108
|
+
expect(process.env.ONLY_DEV).toBe('yes');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle empty env files', () => {
|
|
112
|
+
writeFileSync(join(testDir, '.env'), '');
|
|
113
|
+
|
|
114
|
+
const { loaded, missing } = loadEnvFiles('.env', testDir);
|
|
115
|
+
|
|
116
|
+
expect(loaded).toEqual(['.env']);
|
|
117
|
+
expect(missing).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle env files with comments', () => {
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(testDir, '.env'),
|
|
123
|
+
'# This is a comment\nVAR=value\n# Another comment',
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const { loaded } = loadEnvFiles('.env', testDir);
|
|
127
|
+
|
|
128
|
+
expect(loaded).toEqual(['.env']);
|
|
129
|
+
expect(process.env.VAR).toBe('value');
|
|
130
|
+
});
|
|
131
|
+
});
|