@leo-h/create-nodejs-app 1.0.43 → 1.0.45
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/package.json +2 -2
- package/templates/clean/package.json +1 -1
- package/templates/clean/pnpm-lock.yaml +51 -51
- package/templates/fastify/.lintstagedrc.json +1 -1
- package/templates/fastify/package.json +2 -0
- package/templates/fastify/pnpm-lock.yaml +32 -0
- package/templates/fastify/src/core/create-controller-response-schema.ts +12 -3
- package/templates/fastify/src/env.ts +19 -6
- package/templates/fastify/src/http/app.ts +9 -2
- package/templates/fastify/src/http/controllers/hello-multipart.controller.spec.ts +122 -0
- package/templates/fastify/src/http/controllers/hello-multipart.controller.ts +67 -0
- package/templates/fastify/src/http/controllers/hello.controller.ts +3 -3
- package/templates/fastify/src/http/errors.ts +1 -1
- package/templates/fastify/src/http/plugins/error-handler.plugin.ts +44 -8
- package/templates/fastify/src/http/plugins/multipart-form-data.plugin.ts +241 -0
- package/templates/fastify/src/http/plugins/swagger-file-zod-schema-transform.plugin.ts +45 -0
- package/templates/fastify/test/integration/create-safe-form-data.ts +18 -0
- package/templates/fastify/vitest.config.mts +1 -0
- package/templates/nest/package.json +1 -1
- package/templates/nest/pnpm-lock.yaml +40 -40
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@leo-h/create-nodejs-app",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.45",
|
4
4
|
"packageManager": "pnpm@9.15.9",
|
5
5
|
"author": "Leonardo Henrique <leonardo0507.business@gmail.com>",
|
6
6
|
"description": "Create a modern Node.js app with TypeScript using one command.",
|
@@ -62,7 +62,7 @@
|
|
62
62
|
"prettier": "3.5.3",
|
63
63
|
"rimraf": "6.0.1",
|
64
64
|
"tsx": "4.19.3",
|
65
|
-
"typescript": "5.8.
|
65
|
+
"typescript": "5.8.3",
|
66
66
|
"typescript-eslint": "8.28.0",
|
67
67
|
"unbuild": "3.5.0",
|
68
68
|
"vite-tsconfig-paths": "5.1.4",
|
@@ -26,7 +26,7 @@ importers:
|
|
26
26
|
version: 22.13.14
|
27
27
|
'@vitest/eslint-plugin':
|
28
28
|
specifier: 1.1.38
|
29
|
-
version: 1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
29
|
+
version: 1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)(vitest@1.6.1(@types/node@22.13.14))
|
30
30
|
eslint:
|
31
31
|
specifier: 9.23.0
|
32
32
|
version: 9.23.0(jiti@2.4.2)
|
@@ -52,17 +52,17 @@ importers:
|
|
52
52
|
specifier: 4.19.3
|
53
53
|
version: 4.19.3
|
54
54
|
typescript:
|
55
|
-
specifier: 5.8.
|
56
|
-
version: 5.8.
|
55
|
+
specifier: 5.8.3
|
56
|
+
version: 5.8.3
|
57
57
|
typescript-eslint:
|
58
58
|
specifier: 8.28.0
|
59
|
-
version: 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
59
|
+
version: 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
60
60
|
unbuild:
|
61
61
|
specifier: 3.5.0
|
62
|
-
version: 3.5.0(typescript@5.8.
|
62
|
+
version: 3.5.0(typescript@5.8.3)
|
63
63
|
vite-tsconfig-paths:
|
64
64
|
specifier: 5.1.4
|
65
|
-
version: 5.1.4(typescript@5.8.
|
65
|
+
version: 5.1.4(typescript@5.8.3)(vite@5.2.13(@types/node@22.13.14))
|
66
66
|
vitest:
|
67
67
|
specifier: 1.6.1
|
68
68
|
version: 1.6.1(@types/node@22.13.14)
|
@@ -2180,8 +2180,8 @@ packages:
|
|
2180
2180
|
eslint: ^8.57.0 || ^9.0.0
|
2181
2181
|
typescript: '>=4.8.4 <5.9.0'
|
2182
2182
|
|
2183
|
-
typescript@5.8.
|
2184
|
-
resolution: {integrity: sha512-
|
2183
|
+
typescript@5.8.3:
|
2184
|
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
2185
2185
|
engines: {node: '>=14.17'}
|
2186
2186
|
hasBin: true
|
2187
2187
|
|
@@ -2821,32 +2821,32 @@ snapshots:
|
|
2821
2821
|
|
2822
2822
|
'@types/resolve@1.20.2': {}
|
2823
2823
|
|
2824
|
-
'@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2824
|
+
'@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)':
|
2825
2825
|
dependencies:
|
2826
2826
|
'@eslint-community/regexpp': 4.12.1
|
2827
|
-
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2827
|
+
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
2828
2828
|
'@typescript-eslint/scope-manager': 8.28.0
|
2829
|
-
'@typescript-eslint/type-utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2830
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2829
|
+
'@typescript-eslint/type-utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
2830
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
2831
2831
|
'@typescript-eslint/visitor-keys': 8.28.0
|
2832
2832
|
eslint: 9.23.0(jiti@2.4.2)
|
2833
2833
|
graphemer: 1.4.0
|
2834
2834
|
ignore: 5.3.1
|
2835
2835
|
natural-compare: 1.4.0
|
2836
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
2837
|
-
typescript: 5.8.
|
2836
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
2837
|
+
typescript: 5.8.3
|
2838
2838
|
transitivePeerDependencies:
|
2839
2839
|
- supports-color
|
2840
2840
|
|
2841
|
-
'@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2841
|
+
'@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)':
|
2842
2842
|
dependencies:
|
2843
2843
|
'@typescript-eslint/scope-manager': 8.28.0
|
2844
2844
|
'@typescript-eslint/types': 8.28.0
|
2845
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
2845
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
2846
2846
|
'@typescript-eslint/visitor-keys': 8.28.0
|
2847
2847
|
debug: 4.4.0
|
2848
2848
|
eslint: 9.23.0(jiti@2.4.2)
|
2849
|
-
typescript: 5.8.
|
2849
|
+
typescript: 5.8.3
|
2850
2850
|
transitivePeerDependencies:
|
2851
2851
|
- supports-color
|
2852
2852
|
|
@@ -2855,20 +2855,20 @@ snapshots:
|
|
2855
2855
|
'@typescript-eslint/types': 8.28.0
|
2856
2856
|
'@typescript-eslint/visitor-keys': 8.28.0
|
2857
2857
|
|
2858
|
-
'@typescript-eslint/type-utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2858
|
+
'@typescript-eslint/type-utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)':
|
2859
2859
|
dependencies:
|
2860
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
2861
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2860
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
2861
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
2862
2862
|
debug: 4.4.0
|
2863
2863
|
eslint: 9.23.0(jiti@2.4.2)
|
2864
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
2865
|
-
typescript: 5.8.
|
2864
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
2865
|
+
typescript: 5.8.3
|
2866
2866
|
transitivePeerDependencies:
|
2867
2867
|
- supports-color
|
2868
2868
|
|
2869
2869
|
'@typescript-eslint/types@8.28.0': {}
|
2870
2870
|
|
2871
|
-
'@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.
|
2871
|
+
'@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.3)':
|
2872
2872
|
dependencies:
|
2873
2873
|
'@typescript-eslint/types': 8.28.0
|
2874
2874
|
'@typescript-eslint/visitor-keys': 8.28.0
|
@@ -2877,19 +2877,19 @@ snapshots:
|
|
2877
2877
|
is-glob: 4.0.3
|
2878
2878
|
minimatch: 9.0.4
|
2879
2879
|
semver: 7.7.1
|
2880
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
2881
|
-
typescript: 5.8.
|
2880
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
2881
|
+
typescript: 5.8.3
|
2882
2882
|
transitivePeerDependencies:
|
2883
2883
|
- supports-color
|
2884
2884
|
|
2885
|
-
'@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2885
|
+
'@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)':
|
2886
2886
|
dependencies:
|
2887
2887
|
'@eslint-community/eslint-utils': 4.4.0(eslint@9.23.0(jiti@2.4.2))
|
2888
2888
|
'@typescript-eslint/scope-manager': 8.28.0
|
2889
2889
|
'@typescript-eslint/types': 8.28.0
|
2890
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
2890
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
2891
2891
|
eslint: 9.23.0(jiti@2.4.2)
|
2892
|
-
typescript: 5.8.
|
2892
|
+
typescript: 5.8.3
|
2893
2893
|
transitivePeerDependencies:
|
2894
2894
|
- supports-color
|
2895
2895
|
|
@@ -2898,12 +2898,12 @@ snapshots:
|
|
2898
2898
|
'@typescript-eslint/types': 8.28.0
|
2899
2899
|
eslint-visitor-keys: 4.2.0
|
2900
2900
|
|
2901
|
-
'@vitest/eslint-plugin@1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2901
|
+
'@vitest/eslint-plugin@1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)(vitest@1.6.1(@types/node@22.13.14))':
|
2902
2902
|
dependencies:
|
2903
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
2903
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
2904
2904
|
eslint: 9.23.0(jiti@2.4.2)
|
2905
2905
|
optionalDependencies:
|
2906
|
-
typescript: 5.8.
|
2906
|
+
typescript: 5.8.3
|
2907
2907
|
vitest: 1.6.1(@types/node@22.13.14)
|
2908
2908
|
|
2909
2909
|
'@vitest/expect@1.6.1':
|
@@ -3688,7 +3688,7 @@ snapshots:
|
|
3688
3688
|
|
3689
3689
|
minipass@7.1.2: {}
|
3690
3690
|
|
3691
|
-
mkdist@2.2.0(typescript@5.8.
|
3691
|
+
mkdist@2.2.0(typescript@5.8.3):
|
3692
3692
|
dependencies:
|
3693
3693
|
autoprefixer: 10.4.20(postcss@8.5.3)
|
3694
3694
|
citty: 0.1.6
|
@@ -3704,7 +3704,7 @@ snapshots:
|
|
3704
3704
|
semver: 7.7.1
|
3705
3705
|
tinyglobby: 0.2.12
|
3706
3706
|
optionalDependencies:
|
3707
|
-
typescript: 5.8.
|
3707
|
+
typescript: 5.8.3
|
3708
3708
|
|
3709
3709
|
mlly@1.7.1:
|
3710
3710
|
dependencies:
|
@@ -4041,11 +4041,11 @@ snapshots:
|
|
4041
4041
|
glob: 11.0.0
|
4042
4042
|
package-json-from-dist: 1.0.1
|
4043
4043
|
|
4044
|
-
rollup-plugin-dts@6.1.1(rollup@4.34.8)(typescript@5.8.
|
4044
|
+
rollup-plugin-dts@6.1.1(rollup@4.34.8)(typescript@5.8.3):
|
4045
4045
|
dependencies:
|
4046
4046
|
magic-string: 0.30.17
|
4047
4047
|
rollup: 4.34.8
|
4048
|
-
typescript: 5.8.
|
4048
|
+
typescript: 5.8.3
|
4049
4049
|
optionalDependencies:
|
4050
4050
|
'@babel/code-frame': 7.24.7
|
4051
4051
|
|
@@ -4210,13 +4210,13 @@ snapshots:
|
|
4210
4210
|
dependencies:
|
4211
4211
|
is-number: 7.0.0
|
4212
4212
|
|
4213
|
-
ts-api-utils@2.1.0(typescript@5.8.
|
4213
|
+
ts-api-utils@2.1.0(typescript@5.8.3):
|
4214
4214
|
dependencies:
|
4215
|
-
typescript: 5.8.
|
4215
|
+
typescript: 5.8.3
|
4216
4216
|
|
4217
|
-
tsconfck@3.1.0(typescript@5.8.
|
4217
|
+
tsconfck@3.1.0(typescript@5.8.3):
|
4218
4218
|
optionalDependencies:
|
4219
|
-
typescript: 5.8.
|
4219
|
+
typescript: 5.8.3
|
4220
4220
|
|
4221
4221
|
tsx@4.19.3:
|
4222
4222
|
dependencies:
|
@@ -4231,23 +4231,23 @@ snapshots:
|
|
4231
4231
|
|
4232
4232
|
type-detect@4.0.8: {}
|
4233
4233
|
|
4234
|
-
typescript-eslint@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
4234
|
+
typescript-eslint@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3):
|
4235
4235
|
dependencies:
|
4236
|
-
'@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
4237
|
-
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
4238
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.
|
4236
|
+
'@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
4237
|
+
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
4238
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)
|
4239
4239
|
eslint: 9.23.0(jiti@2.4.2)
|
4240
|
-
typescript: 5.8.
|
4240
|
+
typescript: 5.8.3
|
4241
4241
|
transitivePeerDependencies:
|
4242
4242
|
- supports-color
|
4243
4243
|
|
4244
|
-
typescript@5.8.
|
4244
|
+
typescript@5.8.3: {}
|
4245
4245
|
|
4246
4246
|
ufo@1.5.3: {}
|
4247
4247
|
|
4248
4248
|
ufo@1.5.4: {}
|
4249
4249
|
|
4250
|
-
unbuild@3.5.0(typescript@5.8.
|
4250
|
+
unbuild@3.5.0(typescript@5.8.3):
|
4251
4251
|
dependencies:
|
4252
4252
|
'@rollup/plugin-alias': 5.1.1(rollup@4.34.8)
|
4253
4253
|
'@rollup/plugin-commonjs': 28.0.2(rollup@4.34.8)
|
@@ -4263,18 +4263,18 @@ snapshots:
|
|
4263
4263
|
hookable: 5.5.3
|
4264
4264
|
jiti: 2.4.2
|
4265
4265
|
magic-string: 0.30.17
|
4266
|
-
mkdist: 2.2.0(typescript@5.8.
|
4266
|
+
mkdist: 2.2.0(typescript@5.8.3)
|
4267
4267
|
mlly: 1.7.4
|
4268
4268
|
pathe: 2.0.3
|
4269
4269
|
pkg-types: 2.0.0
|
4270
4270
|
pretty-bytes: 6.1.1
|
4271
4271
|
rollup: 4.34.8
|
4272
|
-
rollup-plugin-dts: 6.1.1(rollup@4.34.8)(typescript@5.8.
|
4272
|
+
rollup-plugin-dts: 6.1.1(rollup@4.34.8)(typescript@5.8.3)
|
4273
4273
|
scule: 1.3.0
|
4274
4274
|
tinyglobby: 0.2.12
|
4275
4275
|
untyped: 2.0.0
|
4276
4276
|
optionalDependencies:
|
4277
|
-
typescript: 5.8.
|
4277
|
+
typescript: 5.8.3
|
4278
4278
|
transitivePeerDependencies:
|
4279
4279
|
- sass
|
4280
4280
|
- vue
|
@@ -4319,11 +4319,11 @@ snapshots:
|
|
4319
4319
|
- supports-color
|
4320
4320
|
- terser
|
4321
4321
|
|
4322
|
-
vite-tsconfig-paths@5.1.4(typescript@5.8.
|
4322
|
+
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@5.2.13(@types/node@22.13.14)):
|
4323
4323
|
dependencies:
|
4324
4324
|
debug: 4.4.0
|
4325
4325
|
globrex: 0.1.2
|
4326
|
-
tsconfck: 3.1.0(typescript@5.8.
|
4326
|
+
tsconfck: 3.1.0(typescript@5.8.3)
|
4327
4327
|
optionalDependencies:
|
4328
4328
|
vite: 5.2.13(@types/node@22.13.14)
|
4329
4329
|
transitivePeerDependencies:
|
@@ -18,10 +18,12 @@
|
|
18
18
|
"dependencies": {
|
19
19
|
"@fastify/cookie": "11.0.2",
|
20
20
|
"@fastify/cors": "11.0.1",
|
21
|
+
"@fastify/multipart": "9.0.3",
|
21
22
|
"@fastify/swagger": "9.4.2",
|
22
23
|
"@fastify/swagger-ui": "5.2.2",
|
23
24
|
"dotenv": "16.4.7",
|
24
25
|
"fastify": "5.3.2",
|
26
|
+
"fastify-plugin": "5.0.1",
|
25
27
|
"fastify-type-provider-zod": "4.0.2",
|
26
28
|
"zod": "3.24.2"
|
27
29
|
},
|
@@ -14,6 +14,9 @@ importers:
|
|
14
14
|
'@fastify/cors':
|
15
15
|
specifier: 11.0.1
|
16
16
|
version: 11.0.1
|
17
|
+
'@fastify/multipart':
|
18
|
+
specifier: 9.0.3
|
19
|
+
version: 9.0.3
|
17
20
|
'@fastify/swagger':
|
18
21
|
specifier: 9.4.2
|
19
22
|
version: 9.4.2
|
@@ -26,6 +29,9 @@ importers:
|
|
26
29
|
fastify:
|
27
30
|
specifier: 5.3.2
|
28
31
|
version: 5.3.2
|
32
|
+
fastify-plugin:
|
33
|
+
specifier: 5.0.1
|
34
|
+
version: 5.0.1
|
29
35
|
fastify-type-provider-zod:
|
30
36
|
specifier: 4.0.2
|
31
37
|
version: 4.0.2(fastify@5.3.2)(zod@3.24.2)
|
@@ -292,12 +298,18 @@ packages:
|
|
292
298
|
'@fastify/ajv-compiler@4.0.2':
|
293
299
|
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
|
294
300
|
|
301
|
+
'@fastify/busboy@3.1.1':
|
302
|
+
resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==}
|
303
|
+
|
295
304
|
'@fastify/cookie@11.0.2':
|
296
305
|
resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==}
|
297
306
|
|
298
307
|
'@fastify/cors@11.0.1':
|
299
308
|
resolution: {integrity: sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==}
|
300
309
|
|
310
|
+
'@fastify/deepmerge@2.0.2':
|
311
|
+
resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==}
|
312
|
+
|
301
313
|
'@fastify/error@4.1.0':
|
302
314
|
resolution: {integrity: sha512-KeFcciOr1eo/YvIXHP65S94jfEEqn1RxTRBT1aJaHxY5FK0/GDXYozsQMMWlZoHgi8i0s+YtrLsgj/JkUUjSkQ==}
|
303
315
|
|
@@ -310,6 +322,9 @@ packages:
|
|
310
322
|
'@fastify/merge-json-schemas@0.2.1':
|
311
323
|
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
|
312
324
|
|
325
|
+
'@fastify/multipart@9.0.3':
|
326
|
+
resolution: {integrity: sha512-pJogxQCrT12/6I5Fh6jr3narwcymA0pv4B0jbC7c6Bl9wnrxomEUnV0d26w6gUls7gSXmhG8JGRMmHFIPsxt1g==}
|
327
|
+
|
313
328
|
'@fastify/proxy-addr@5.0.0':
|
314
329
|
resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==}
|
315
330
|
|
@@ -1729,6 +1744,9 @@ packages:
|
|
1729
1744
|
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
1730
1745
|
engines: {node: '>=10'}
|
1731
1746
|
|
1747
|
+
secure-json-parse@3.0.2:
|
1748
|
+
resolution: {integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==}
|
1749
|
+
|
1732
1750
|
secure-json-parse@4.0.0:
|
1733
1751
|
resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==}
|
1734
1752
|
|
@@ -2231,6 +2249,8 @@ snapshots:
|
|
2231
2249
|
ajv-formats: 3.0.1(ajv@8.17.1)
|
2232
2250
|
fast-uri: 3.0.6
|
2233
2251
|
|
2252
|
+
'@fastify/busboy@3.1.1': {}
|
2253
|
+
|
2234
2254
|
'@fastify/cookie@11.0.2':
|
2235
2255
|
dependencies:
|
2236
2256
|
cookie: 1.0.2
|
@@ -2241,6 +2261,8 @@ snapshots:
|
|
2241
2261
|
fastify-plugin: 5.0.1
|
2242
2262
|
toad-cache: 3.7.0
|
2243
2263
|
|
2264
|
+
'@fastify/deepmerge@2.0.2': {}
|
2265
|
+
|
2244
2266
|
'@fastify/error@4.1.0': {}
|
2245
2267
|
|
2246
2268
|
'@fastify/fast-json-stringify-compiler@5.0.3':
|
@@ -2253,6 +2275,14 @@ snapshots:
|
|
2253
2275
|
dependencies:
|
2254
2276
|
dequal: 2.0.3
|
2255
2277
|
|
2278
|
+
'@fastify/multipart@9.0.3':
|
2279
|
+
dependencies:
|
2280
|
+
'@fastify/busboy': 3.1.1
|
2281
|
+
'@fastify/deepmerge': 2.0.2
|
2282
|
+
'@fastify/error': 4.1.0
|
2283
|
+
fastify-plugin: 5.0.1
|
2284
|
+
secure-json-parse: 3.0.2
|
2285
|
+
|
2256
2286
|
'@fastify/proxy-addr@5.0.0':
|
2257
2287
|
dependencies:
|
2258
2288
|
'@fastify/forwarded': 3.0.0
|
@@ -3652,6 +3682,8 @@ snapshots:
|
|
3652
3682
|
|
3653
3683
|
safe-stable-stringify@2.5.0: {}
|
3654
3684
|
|
3685
|
+
secure-json-parse@3.0.2: {}
|
3686
|
+
|
3655
3687
|
secure-json-parse@4.0.0: {}
|
3656
3688
|
|
3657
3689
|
seek-bzip@2.0.0:
|
@@ -29,9 +29,18 @@ export function createControllerResponseSchema<
|
|
29
29
|
>
|
30
30
|
: ErrorInstance<ErrorClasses, K>["schema"];
|
31
31
|
} {
|
32
|
-
const errorInstances = errorClasses
|
33
|
-
|
34
|
-
|
32
|
+
const errorInstances = errorClasses
|
33
|
+
.map(ErrorClass => {
|
34
|
+
return new ErrorClass();
|
35
|
+
})
|
36
|
+
.filter((errorInstance, index, array) => {
|
37
|
+
const prevErrorInstances = array.slice(0, index);
|
38
|
+
const errorAlreadyFiltered = prevErrorInstances.some(({ error }) => {
|
39
|
+
return error === errorInstance.error;
|
40
|
+
});
|
41
|
+
|
42
|
+
return !errorAlreadyFiltered;
|
43
|
+
});
|
35
44
|
const statusCodes = [
|
36
45
|
...new Set([
|
37
46
|
...Object.keys(successResponses),
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import packageJson from "@/../package.json";
|
2
2
|
import { config } from "dotenv";
|
3
|
+
import { existsSync, mkdirSync } from "node:fs";
|
3
4
|
import { resolve } from "node:path";
|
4
5
|
import { z } from "zod";
|
5
6
|
|
@@ -21,21 +22,33 @@ if (process.env.npm_lifecycle_event?.includes(":test"))
|
|
21
22
|
if (!process.env.NODE_ENV)
|
22
23
|
throw new Error("Could not set to the environment variables to use.");
|
23
24
|
|
24
|
-
const
|
25
|
-
|
25
|
+
const nodeEnv = process.env.NODE_ENV as keyof typeof envFileNames;
|
26
|
+
const envFileName = envFileNames[nodeEnv];
|
26
27
|
|
27
28
|
config({
|
28
|
-
path: resolve(
|
29
|
+
path: resolve(
|
30
|
+
__dirname,
|
31
|
+
nodeEnv === "production" ? "../../" : "..",
|
32
|
+
envFileName,
|
33
|
+
),
|
29
34
|
override: true,
|
30
35
|
});
|
31
36
|
|
32
37
|
const schema = z.object({
|
33
|
-
NODE_ENV: z
|
34
|
-
.enum(["production", "development", "test"])
|
35
|
-
.default(process.env.NODE_ENV as keyof typeof envFileNames),
|
38
|
+
NODE_ENV: z.enum(["production", "development", "test"]).default(nodeEnv),
|
36
39
|
API_NAME: z.string().default(packageJson.name),
|
37
40
|
API_PORT: z.coerce.number().default(3333),
|
38
41
|
API_ACCESS_PERMISSION_CLIENT_SIDE: z.string().default("*"),
|
42
|
+
TMP_FILES_PATH: z
|
43
|
+
.string()
|
44
|
+
.default("./tmp")
|
45
|
+
.transform(pathFromSrc => {
|
46
|
+
const tmpPath = resolve(__dirname, "..", pathFromSrc);
|
47
|
+
|
48
|
+
if (!existsSync(tmpPath)) mkdirSync(tmpPath);
|
49
|
+
|
50
|
+
return tmpPath;
|
51
|
+
}),
|
39
52
|
});
|
40
53
|
|
41
54
|
const parsedEnv = schema.safeParse(process.env);
|
@@ -12,8 +12,9 @@ import {
|
|
12
12
|
import { env } from "../env";
|
13
13
|
import { errorHandlerPlugin } from "./plugins/error-handler.plugin";
|
14
14
|
import { notFoundErrorHandlerPlugin } from "./plugins/not-found-error-handler.plugin";
|
15
|
-
import { swaggerUiPlugin } from "./plugins/swagger-ui.plugin";
|
16
15
|
import { routesPlugin } from "./plugins/routes.plugin";
|
16
|
+
import { swaggerFileZodSchemaTransformPlugin } from "./plugins/swagger-file-zod-schema-transform.plugin";
|
17
|
+
import { swaggerUiPlugin } from "./plugins/swagger-ui.plugin";
|
17
18
|
|
18
19
|
export const app = fastify().withTypeProvider<ZodTypeProvider>();
|
19
20
|
export const appPrefix = "/v1";
|
@@ -31,7 +32,13 @@ app.register(fastifySwagger, {
|
|
31
32
|
},
|
32
33
|
servers: [],
|
33
34
|
},
|
34
|
-
transform:
|
35
|
+
transform: data => {
|
36
|
+
const jsonSchema = jsonSchemaTransform(data);
|
37
|
+
|
38
|
+
swaggerFileZodSchemaTransformPlugin(data, jsonSchema);
|
39
|
+
|
40
|
+
return jsonSchema;
|
41
|
+
},
|
35
42
|
});
|
36
43
|
app.register(swaggerUiPlugin);
|
37
44
|
app.register(routesPlugin, { prefix: appPrefix });
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { env } from "@/env";
|
2
|
+
import { faker } from "@faker-js/faker";
|
3
|
+
import {
|
4
|
+
createSafeFormData,
|
5
|
+
CreateSafeFormDataOutput,
|
6
|
+
} from "test/integration/create-safe-form-data";
|
7
|
+
import { createTestRequest } from "test/integration/create-test-request";
|
8
|
+
import { OverrideProperties } from "type-fest";
|
9
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
10
|
+
import { appPrefix } from "../app";
|
11
|
+
import { ValidationError } from "../errors";
|
12
|
+
import {
|
13
|
+
HelloMultipartControllerBodyInput,
|
14
|
+
helloMultipartControllerMethod,
|
15
|
+
helloMultipartControllerUrl,
|
16
|
+
} from "./hello-multipart.controller";
|
17
|
+
|
18
|
+
const sutMethod = helloMultipartControllerMethod;
|
19
|
+
const sutUrl = appPrefix + helloMultipartControllerUrl;
|
20
|
+
|
21
|
+
type FormDataInput = OverrideProperties<
|
22
|
+
HelloMultipartControllerBodyInput,
|
23
|
+
{
|
24
|
+
attachment: Blob;
|
25
|
+
}
|
26
|
+
>;
|
27
|
+
|
28
|
+
const createFormData = createSafeFormData<FormDataInput>;
|
29
|
+
|
30
|
+
const sut = createTestRequest<{
|
31
|
+
body: FormData;
|
32
|
+
}>({
|
33
|
+
method: sutMethod,
|
34
|
+
url: sutUrl,
|
35
|
+
});
|
36
|
+
|
37
|
+
describe(`[Controller] ${sutMethod} ${sutUrl}`, () => {
|
38
|
+
let defaultFormDataInput: CreateSafeFormDataOutput<FormDataInput>;
|
39
|
+
|
40
|
+
beforeEach(() => {
|
41
|
+
const randomText = faker.lorem.sentences(2);
|
42
|
+
const randomTextBlob = new Blob([randomText], { type: "text/plain" });
|
43
|
+
|
44
|
+
defaultFormDataInput = createFormData({
|
45
|
+
show: "true",
|
46
|
+
attachment: randomTextBlob,
|
47
|
+
});
|
48
|
+
});
|
49
|
+
|
50
|
+
describe("should not be able to get hello world with multipart/form-data content-type", () => {
|
51
|
+
describe("if invalid input", () => {
|
52
|
+
const error = new ValidationError(null);
|
53
|
+
|
54
|
+
test("with invalid property show", async () => {
|
55
|
+
const formDataInput = createFormData({
|
56
|
+
...defaultFormDataInput.input,
|
57
|
+
// @ts-expect-error the value must be a string
|
58
|
+
show: 0,
|
59
|
+
});
|
60
|
+
|
61
|
+
const result = await sut({
|
62
|
+
body: formDataInput.formData,
|
63
|
+
});
|
64
|
+
|
65
|
+
expect(result.statusCode).toEqual(error.statusCode);
|
66
|
+
expect(result.body).toMatchObject({
|
67
|
+
...error.serialize(),
|
68
|
+
debug: [
|
69
|
+
expect.objectContaining({
|
70
|
+
instancePath: "/show",
|
71
|
+
}),
|
72
|
+
],
|
73
|
+
});
|
74
|
+
});
|
75
|
+
});
|
76
|
+
|
77
|
+
test("if property show is set to false", async () => {
|
78
|
+
const formDataInput = createFormData({
|
79
|
+
...defaultFormDataInput.input,
|
80
|
+
show: "false",
|
81
|
+
});
|
82
|
+
|
83
|
+
const result = await sut({
|
84
|
+
body: formDataInput.formData,
|
85
|
+
});
|
86
|
+
|
87
|
+
const error = new ValidationError(
|
88
|
+
`Você não quer exibir o "Hello world" :(`,
|
89
|
+
);
|
90
|
+
|
91
|
+
expect(result.statusCode).toEqual(error.statusCode);
|
92
|
+
expect(result.body).toStrictEqual(error.serialize());
|
93
|
+
});
|
94
|
+
});
|
95
|
+
|
96
|
+
describe("should be able to get hello world with multipart/form-data content-type", async () => {
|
97
|
+
test("if property show is set to true", async () => {
|
98
|
+
const formDataInput = createFormData({
|
99
|
+
...defaultFormDataInput.input,
|
100
|
+
show: "true",
|
101
|
+
});
|
102
|
+
|
103
|
+
const result = await sut({
|
104
|
+
body: formDataInput.formData,
|
105
|
+
});
|
106
|
+
|
107
|
+
expect(result.statusCode).toEqual(200);
|
108
|
+
expect(result.body).toStrictEqual({
|
109
|
+
message: "Hello world!",
|
110
|
+
attachment: {
|
111
|
+
type: "file",
|
112
|
+
encoding: "7bit",
|
113
|
+
mimetype: "text/plain",
|
114
|
+
fileStream: expect.any(Object),
|
115
|
+
fileOriginalName: "blob",
|
116
|
+
fileName: expect.any(String),
|
117
|
+
filePath: expect.stringContaining(env.TMP_FILES_PATH),
|
118
|
+
},
|
119
|
+
});
|
120
|
+
});
|
121
|
+
});
|
122
|
+
});
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { FastifyZodInstance } from "@/@types/fastify";
|
2
|
+
import { createControllerResponseSchema } from "@/core/create-controller-response-schema";
|
3
|
+
import { z } from "zod";
|
4
|
+
import { InternalServerError, ValidationError } from "../errors";
|
5
|
+
import {
|
6
|
+
MultipartFormDataDiskFile,
|
7
|
+
multipartFormDataPlugin,
|
8
|
+
} from "../plugins/multipart-form-data.plugin";
|
9
|
+
|
10
|
+
export const helloMultipartControllerMethod = "POST" as const;
|
11
|
+
export const helloMultipartControllerUrl = "/hello/multipart" as const;
|
12
|
+
|
13
|
+
export type HelloMultipartControllerBodyInput = z.input<
|
14
|
+
typeof helloMultipartControllerBodySchema
|
15
|
+
>;
|
16
|
+
|
17
|
+
const helloMultipartControllerBodySchema = z.object({
|
18
|
+
show: z
|
19
|
+
.enum(["true", "false"])
|
20
|
+
.transform<boolean>(val => JSON.parse(val))
|
21
|
+
.default("true"),
|
22
|
+
attachment: MultipartFormDataDiskFile.schema,
|
23
|
+
});
|
24
|
+
|
25
|
+
export default function helloMultipartController(app: FastifyZodInstance) {
|
26
|
+
app.register(multipartFormDataPlugin.plugin, {
|
27
|
+
attachments: {
|
28
|
+
storage: "disk",
|
29
|
+
allowedMimeTypes: ["text/plain"],
|
30
|
+
maxCount: 1,
|
31
|
+
maxSize: 1024 * 1024 * 50, // 50MB
|
32
|
+
},
|
33
|
+
});
|
34
|
+
|
35
|
+
app.route({
|
36
|
+
method: helloMultipartControllerMethod,
|
37
|
+
url: helloMultipartControllerUrl,
|
38
|
+
schema: {
|
39
|
+
operationId: "helloMultipartController",
|
40
|
+
tags: ["Hello"],
|
41
|
+
summary: "Hello world!",
|
42
|
+
consumes: ["multipart/form-data"],
|
43
|
+
body: helloMultipartControllerBodySchema,
|
44
|
+
response: createControllerResponseSchema(
|
45
|
+
{
|
46
|
+
200: z.object({
|
47
|
+
message: z.literal("Hello world!"),
|
48
|
+
attachment: z.custom(),
|
49
|
+
}),
|
50
|
+
},
|
51
|
+
InternalServerError,
|
52
|
+
ValidationError,
|
53
|
+
),
|
54
|
+
},
|
55
|
+
handler: async (request, response) => {
|
56
|
+
const { show, attachment } = request.body;
|
57
|
+
|
58
|
+
if (!show)
|
59
|
+
return new ValidationError(`Você não quer exibir o "Hello world" :(`);
|
60
|
+
|
61
|
+
response.status(200).send({
|
62
|
+
message: "Hello world!",
|
63
|
+
attachment: attachment,
|
64
|
+
});
|
65
|
+
},
|
66
|
+
});
|
67
|
+
}
|
@@ -7,10 +7,10 @@ export const helloControllerMethod = "GET" as const;
|
|
7
7
|
export const helloControllerUrl = "/hello" as const;
|
8
8
|
|
9
9
|
export type HelloControllerQueryParamsInput = z.input<
|
10
|
-
typeof
|
10
|
+
typeof helloControllerQueryParamsSchema
|
11
11
|
>;
|
12
12
|
|
13
|
-
const
|
13
|
+
const helloControllerQueryParamsSchema = z.object({
|
14
14
|
show: z
|
15
15
|
.enum(["true", "false"])
|
16
16
|
.transform<boolean>(val => JSON.parse(val))
|
@@ -25,7 +25,7 @@ export default function helloController(app: FastifyZodInstance) {
|
|
25
25
|
operationId: "helloController",
|
26
26
|
tags: ["Hello"],
|
27
27
|
summary: "Hello world!",
|
28
|
-
querystring:
|
28
|
+
querystring: helloControllerQueryParamsSchema,
|
29
29
|
response: createControllerResponseSchema(
|
30
30
|
{
|
31
31
|
200: z.object({
|
@@ -35,7 +35,7 @@ export class InternalServerError extends BaseError {
|
|
35
35
|
public readonly error = "INTERNAL_SERVER_ERROR";
|
36
36
|
public readonly statusCode = 500;
|
37
37
|
public readonly message =
|
38
|
-
"Desculpe, um erro inesperado ocorreu. Tente novamente alguns minutos ou nos contate.";
|
38
|
+
"Desculpe, um erro inesperado ocorreu. Tente novamente em alguns minutos ou nos contate.";
|
39
39
|
|
40
40
|
public constructor(public debug: unknown) {
|
41
41
|
super();
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { FastifyZodReply, FastifyZodRequest } from "@/@types/fastify";
|
2
|
+
import { FastifyError } from "fastify";
|
2
3
|
import { hasZodFastifySchemaValidationErrors } from "fastify-type-provider-zod";
|
3
4
|
import { BaseError, InternalServerError, ValidationError } from "../errors";
|
5
|
+
import { multipartFormDataPluginErrorCodes } from "./multipart-form-data.plugin";
|
4
6
|
|
5
7
|
export function errorHandlerPlugin(
|
6
8
|
error: unknown,
|
@@ -9,14 +11,48 @@ export function errorHandlerPlugin(
|
|
9
11
|
) {
|
10
12
|
let httpError: BaseError;
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
switch (true) {
|
15
|
+
case error instanceof BaseError: {
|
16
|
+
httpError = error;
|
17
|
+
break;
|
18
|
+
}
|
19
|
+
|
20
|
+
case error instanceof SyntaxError: {
|
21
|
+
httpError = new ValidationError(error.message);
|
22
|
+
break;
|
23
|
+
}
|
24
|
+
|
25
|
+
case hasZodFastifySchemaValidationErrors(error): {
|
26
|
+
httpError = new ValidationError(error.validation);
|
27
|
+
break;
|
28
|
+
}
|
29
|
+
|
30
|
+
case error instanceof Error && error.name === "FastifyError": {
|
31
|
+
const fastifyError = error as FastifyError;
|
32
|
+
|
33
|
+
if (fastifyError.code.startsWith("FST_ERR_CTP")) {
|
34
|
+
httpError = new ValidationError(error);
|
35
|
+
break;
|
36
|
+
}
|
37
|
+
|
38
|
+
const multipartErrorCodes =
|
39
|
+
multipartFormDataPluginErrorCodes as unknown as string[];
|
40
|
+
|
41
|
+
if (multipartErrorCodes.includes(fastifyError.code)) {
|
42
|
+
if ("part" in error) delete error.part;
|
43
|
+
|
44
|
+
httpError = new ValidationError(error);
|
45
|
+
break;
|
46
|
+
}
|
47
|
+
|
48
|
+
httpError = new InternalServerError(error);
|
49
|
+
break;
|
50
|
+
}
|
51
|
+
|
52
|
+
default: {
|
53
|
+
httpError = new InternalServerError(error);
|
54
|
+
break;
|
55
|
+
}
|
20
56
|
}
|
21
57
|
|
22
58
|
const isCriticalError = httpError.statusCode.toString().startsWith("5");
|
@@ -0,0 +1,241 @@
|
|
1
|
+
import { FastifyZodInstance } from "@/@types/fastify";
|
2
|
+
import { env } from "@/env";
|
3
|
+
import fastifyMultipart, { MultipartFile } from "@fastify/multipart";
|
4
|
+
import { errorCodes } from "fastify";
|
5
|
+
import fastifyPlugin from "fastify-plugin";
|
6
|
+
import { randomUUID } from "node:crypto";
|
7
|
+
import { createWriteStream } from "node:fs";
|
8
|
+
import { rm } from "node:fs/promises";
|
9
|
+
import { basename, extname, resolve } from "node:path";
|
10
|
+
import { pipeline } from "node:stream/promises";
|
11
|
+
import { z } from "zod";
|
12
|
+
import { ValidationError } from "../errors";
|
13
|
+
|
14
|
+
type InMemoryAttachmentStorage = {
|
15
|
+
/**
|
16
|
+
* File storage strategy.
|
17
|
+
*
|
18
|
+
* @default "disk"
|
19
|
+
*/
|
20
|
+
storage?: "in-memory";
|
21
|
+
};
|
22
|
+
|
23
|
+
type DiskAttachmentStorage = {
|
24
|
+
storage?: "disk";
|
25
|
+
/**
|
26
|
+
* File storage directory.
|
27
|
+
*
|
28
|
+
* @default env.TMP_FILES_PATH
|
29
|
+
*/
|
30
|
+
destinationPath?: string;
|
31
|
+
/**
|
32
|
+
* Remove files after route handler execution
|
33
|
+
*
|
34
|
+
* @default true
|
35
|
+
*/
|
36
|
+
removeAfterHandlerExecution?: boolean;
|
37
|
+
};
|
38
|
+
|
39
|
+
type AttachmentsStorage = InMemoryAttachmentStorage | DiskAttachmentStorage;
|
40
|
+
|
41
|
+
type MultipartFormDataPluginOptions = {
|
42
|
+
attachments?: AttachmentsStorage & {
|
43
|
+
/**
|
44
|
+
* Allowed mime types of files.
|
45
|
+
*
|
46
|
+
* @example ["image/webp", "image/jpeg"]
|
47
|
+
*
|
48
|
+
* @default []
|
49
|
+
*/
|
50
|
+
allowedMimeTypes?: string[];
|
51
|
+
/**
|
52
|
+
* Maximum number of files allowed.
|
53
|
+
*
|
54
|
+
* @default 1
|
55
|
+
*/
|
56
|
+
maxCount?: number;
|
57
|
+
/**
|
58
|
+
* Maximum size allowed for each file in bytes.
|
59
|
+
*
|
60
|
+
* @default 1024 * 1024 * 10 // 10MB
|
61
|
+
*/
|
62
|
+
maxSize?: number;
|
63
|
+
};
|
64
|
+
};
|
65
|
+
|
66
|
+
const plugin = fastifyPlugin(
|
67
|
+
(app: FastifyZodInstance, _options: MultipartFormDataPluginOptions, done) => {
|
68
|
+
const options = {
|
69
|
+
..._options,
|
70
|
+
attachments: {
|
71
|
+
storage: "disk",
|
72
|
+
destinationPath: env.TMP_FILES_PATH,
|
73
|
+
removeAfterHandlerExecution: true,
|
74
|
+
allowedMimeTypes: [],
|
75
|
+
maxCount: 1,
|
76
|
+
maxSize: 1024 * 1024 * 10, // 10MB
|
77
|
+
..._options.attachments,
|
78
|
+
},
|
79
|
+
} satisfies MultipartFormDataPluginOptions;
|
80
|
+
|
81
|
+
app.addHook("onRequest", async request => {
|
82
|
+
const contentType = request.headers["content-type"];
|
83
|
+
const { FST_ERR_CTP_INVALID_MEDIA_TYPE } = errorCodes;
|
84
|
+
|
85
|
+
if (!contentType?.startsWith("multipart/form-data")) {
|
86
|
+
throw new ValidationError(FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType));
|
87
|
+
}
|
88
|
+
});
|
89
|
+
|
90
|
+
app.register(fastifyMultipart, {
|
91
|
+
attachFieldsToBody: "keyValues",
|
92
|
+
limits: {
|
93
|
+
fieldNameSize: 100,
|
94
|
+
fieldSize: 1024 * 1024, // 1MB
|
95
|
+
fields: Infinity,
|
96
|
+
parts: Infinity,
|
97
|
+
headerPairs: 2000,
|
98
|
+
files: options.attachments.maxCount,
|
99
|
+
fileSize: options.attachments.maxSize,
|
100
|
+
},
|
101
|
+
async onFile(file) {
|
102
|
+
const allowedMimeTypes = options.attachments
|
103
|
+
.allowedMimeTypes as string[];
|
104
|
+
|
105
|
+
if (
|
106
|
+
!allowedMimeTypes.includes(file.mimetype) &&
|
107
|
+
!allowedMimeTypes.length
|
108
|
+
) {
|
109
|
+
throw new ValidationError(
|
110
|
+
`Nenhum MIME type de arquivo foi configurado para ser aceito.`,
|
111
|
+
);
|
112
|
+
}
|
113
|
+
|
114
|
+
if (
|
115
|
+
!allowedMimeTypes.includes(file.mimetype) &&
|
116
|
+
allowedMimeTypes.length
|
117
|
+
) {
|
118
|
+
throw new ValidationError(
|
119
|
+
`Apenas os seguintes MIME types são aceitos: ${allowedMimeTypes.join(", ")}.`,
|
120
|
+
);
|
121
|
+
}
|
122
|
+
|
123
|
+
if (options.attachments.storage === "in-memory") {
|
124
|
+
const buffer = await file.toBuffer();
|
125
|
+
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
127
|
+
(file as any).value = new MultipartFormDataInMemoryFile(file, buffer);
|
128
|
+
return;
|
129
|
+
}
|
130
|
+
|
131
|
+
const uniqueFileName = randomUUID() + extname(file.filename);
|
132
|
+
const filePath = resolve(env.TMP_FILES_PATH, uniqueFileName);
|
133
|
+
|
134
|
+
await pipeline(file.file, createWriteStream(filePath));
|
135
|
+
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
137
|
+
(file as any).value = new MultipartFormDataDiskFile(file, filePath);
|
138
|
+
},
|
139
|
+
});
|
140
|
+
|
141
|
+
if (
|
142
|
+
options.attachments.storage === "disk" &&
|
143
|
+
options.attachments.removeAfterHandlerExecution
|
144
|
+
) {
|
145
|
+
app.addHook("onResponse", async request => {
|
146
|
+
if (!request.body || typeof request.body !== "object") return;
|
147
|
+
|
148
|
+
const bodyDiskFiles = Object.values(request.body)
|
149
|
+
.filter(value => {
|
150
|
+
const valueIsDiskFile = value instanceof MultipartFormDataDiskFile;
|
151
|
+
const valueIsArrayOfDiskFile =
|
152
|
+
Array.isArray(value) &&
|
153
|
+
value.some(item => item instanceof MultipartFormDataDiskFile);
|
154
|
+
|
155
|
+
return valueIsDiskFile || valueIsArrayOfDiskFile;
|
156
|
+
})
|
157
|
+
.flat() as MultipartFormDataDiskFile[];
|
158
|
+
|
159
|
+
if (!bodyDiskFiles.length) return;
|
160
|
+
|
161
|
+
await Promise.all(
|
162
|
+
bodyDiskFiles.map(diskFile => rm(diskFile.filePath, { force: true })),
|
163
|
+
);
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
done();
|
168
|
+
},
|
169
|
+
);
|
170
|
+
|
171
|
+
export const multipartFormDataPlugin = Object.freeze({
|
172
|
+
plugin,
|
173
|
+
} as const);
|
174
|
+
|
175
|
+
export const multipartFormDataPluginErrorCodes = [
|
176
|
+
"FST_PARTS_LIMIT",
|
177
|
+
"FST_FILES_LIMIT",
|
178
|
+
"FST_FIELDS_LIMIT",
|
179
|
+
"FST_REQ_FILE_TOO_LARGE",
|
180
|
+
"FST_PROTO_VIOLATION",
|
181
|
+
"FST_INVALID_MULTIPART_CONTENT_TYPE",
|
182
|
+
"FST_INVALID_JSON_FIELD_ERROR",
|
183
|
+
"FST_FILE_BUFFER_NOT_FOUND",
|
184
|
+
"FST_NO_FORM_DATA",
|
185
|
+
] as const;
|
186
|
+
|
187
|
+
export const MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION =
|
188
|
+
"MULTIPART_FORM_DATA_ATTACHMENT";
|
189
|
+
|
190
|
+
export class MultipartFormDataFile {
|
191
|
+
public type: MultipartFile["type"];
|
192
|
+
public encoding: MultipartFile["encoding"];
|
193
|
+
public mimetype: MultipartFile["mimetype"];
|
194
|
+
public fileStream: MultipartFile["file"];
|
195
|
+
public fileOriginalName: MultipartFile["filename"];
|
196
|
+
|
197
|
+
public constructor(multipartFile: MultipartFile) {
|
198
|
+
this.type = multipartFile.type;
|
199
|
+
this.encoding = multipartFile.encoding;
|
200
|
+
this.mimetype = multipartFile.mimetype;
|
201
|
+
this.fileStream = multipartFile.file;
|
202
|
+
this.fileOriginalName = multipartFile.filename;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
export class MultipartFormDataDiskFile extends MultipartFormDataFile {
|
207
|
+
public fileName: string;
|
208
|
+
public filePath: string;
|
209
|
+
|
210
|
+
public constructor(multipartFile: MultipartFile, filePath: string) {
|
211
|
+
super(multipartFile);
|
212
|
+
|
213
|
+
this.fileName = basename(filePath);
|
214
|
+
this.filePath = filePath;
|
215
|
+
}
|
216
|
+
|
217
|
+
public static get schema() {
|
218
|
+
return z
|
219
|
+
.instanceof(MultipartFormDataDiskFile)
|
220
|
+
.describe(MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION);
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
export class MultipartFormDataInMemoryFile extends MultipartFormDataFile {
|
225
|
+
public buffer: Buffer<ArrayBufferLike>;
|
226
|
+
|
227
|
+
public constructor(
|
228
|
+
multipartFile: MultipartFile,
|
229
|
+
buffer: Buffer<ArrayBufferLike>,
|
230
|
+
) {
|
231
|
+
super(multipartFile);
|
232
|
+
|
233
|
+
this.buffer = buffer;
|
234
|
+
}
|
235
|
+
|
236
|
+
public static get schema() {
|
237
|
+
return z
|
238
|
+
.instanceof(MultipartFormDataInMemoryFile)
|
239
|
+
.describe(MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION);
|
240
|
+
}
|
241
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { FastifyDynamicSwaggerOptions } from "@fastify/swagger";
|
2
|
+
import { jsonSchemaTransform } from "fastify-type-provider-zod";
|
3
|
+
import { ZodArray, ZodObject } from "zod";
|
4
|
+
import { MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION } from "./multipart-form-data.plugin";
|
5
|
+
|
6
|
+
type RouteData = Parameters<
|
7
|
+
NonNullable<FastifyDynamicSwaggerOptions["transform"]>
|
8
|
+
>[0];
|
9
|
+
|
10
|
+
type JsonSchema = ReturnType<typeof jsonSchemaTransform>;
|
11
|
+
|
12
|
+
export function swaggerFileZodSchemaTransformPlugin(
|
13
|
+
routeData: RouteData,
|
14
|
+
jsonSchema: JsonSchema,
|
15
|
+
) {
|
16
|
+
if (
|
17
|
+
routeData.schema.consumes?.includes("multipart/form-data") &&
|
18
|
+
routeData.schema.body instanceof ZodObject
|
19
|
+
) {
|
20
|
+
for (const bodyFieldName in routeData.schema.body.shape) {
|
21
|
+
const bodyFieldSchema = routeData.schema.body.shape[bodyFieldName];
|
22
|
+
|
23
|
+
if (
|
24
|
+
bodyFieldSchema._def.description ===
|
25
|
+
MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION
|
26
|
+
) {
|
27
|
+
jsonSchema.schema.body.properties[bodyFieldName] = {
|
28
|
+
type: "string",
|
29
|
+
format: "binary",
|
30
|
+
};
|
31
|
+
}
|
32
|
+
|
33
|
+
if (
|
34
|
+
bodyFieldSchema instanceof ZodArray &&
|
35
|
+
bodyFieldSchema._def.type._def.description ===
|
36
|
+
MULTIPART_FORM_DATA_FILE_SCHEMA_DESCRIPTION
|
37
|
+
) {
|
38
|
+
jsonSchema.schema.body.properties[bodyFieldName] = {
|
39
|
+
type: "array",
|
40
|
+
items: { type: "string", format: "binary" },
|
41
|
+
};
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
type CreateSafeFormDataInput = Record<string, string | Blob>;
|
2
|
+
|
3
|
+
export type CreateSafeFormDataOutput<Input extends CreateSafeFormDataInput> =
|
4
|
+
ReturnType<typeof createSafeFormData<Input>>;
|
5
|
+
|
6
|
+
export function createSafeFormData<Input extends CreateSafeFormDataInput>(
|
7
|
+
input: Input,
|
8
|
+
) {
|
9
|
+
const formData = new FormData();
|
10
|
+
|
11
|
+
for (const inputProperty in input)
|
12
|
+
formData.set(inputProperty, input[inputProperty]);
|
13
|
+
|
14
|
+
return {
|
15
|
+
input,
|
16
|
+
formData,
|
17
|
+
};
|
18
|
+
}
|
@@ -59,7 +59,7 @@ importers:
|
|
59
59
|
version: 10.4.9(@swc/cli@0.6.0(@swc/core@1.11.13)(chokidar@3.6.0))(@swc/core@1.11.13)
|
60
60
|
'@nestjs/schematics':
|
61
61
|
specifier: 10.2.3
|
62
|
-
version: 10.2.3(chokidar@3.6.0)(typescript@5.8.
|
62
|
+
version: 10.2.3(chokidar@3.6.0)(typescript@5.8.3)
|
63
63
|
'@nestjs/testing':
|
64
64
|
specifier: 10.4.15
|
65
65
|
version: 10.4.15(@nestjs/common@10.4.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.3.10)
|
@@ -83,7 +83,7 @@ importers:
|
|
83
83
|
version: 1.6.1(vitest@1.6.1(@types/node@22.13.14)(terser@5.31.3))
|
84
84
|
'@vitest/eslint-plugin':
|
85
85
|
specifier: 1.1.38
|
86
|
-
version: 1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.
|
86
|
+
version: 1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.3))(eslint@9.23.0)(typescript@5.8.3)(vitest@1.6.1(@types/node@22.13.14)(terser@5.31.3))
|
87
87
|
eslint:
|
88
88
|
specifier: 9.23.0
|
89
89
|
version: 9.23.0
|
@@ -109,11 +109,11 @@ importers:
|
|
109
109
|
specifier: 4.38.0
|
110
110
|
version: 4.38.0
|
111
111
|
typescript:
|
112
|
-
specifier: 5.8.
|
113
|
-
version: 5.8.
|
112
|
+
specifier: 5.8.3
|
113
|
+
version: 5.8.3
|
114
114
|
typescript-eslint:
|
115
115
|
specifier: 8.28.0
|
116
|
-
version: 8.28.0(eslint@9.23.0)(typescript@5.8.
|
116
|
+
version: 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
117
117
|
unplugin-swc:
|
118
118
|
specifier: 1.5.1
|
119
119
|
version: 1.5.1(@swc/core@1.11.13)(rollup@4.19.0)
|
@@ -3049,8 +3049,8 @@ packages:
|
|
3049
3049
|
engines: {node: '>=14.17'}
|
3050
3050
|
hasBin: true
|
3051
3051
|
|
3052
|
-
typescript@5.8.
|
3053
|
-
resolution: {integrity: sha512-
|
3052
|
+
typescript@5.8.3:
|
3053
|
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
3054
3054
|
engines: {node: '>=14.17'}
|
3055
3055
|
hasBin: true
|
3056
3056
|
|
@@ -3664,14 +3664,14 @@ snapshots:
|
|
3664
3664
|
transitivePeerDependencies:
|
3665
3665
|
- chokidar
|
3666
3666
|
|
3667
|
-
'@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.8.
|
3667
|
+
'@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.8.3)':
|
3668
3668
|
dependencies:
|
3669
3669
|
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
|
3670
3670
|
'@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
|
3671
3671
|
comment-json: 4.2.5
|
3672
3672
|
jsonc-parser: 3.3.1
|
3673
3673
|
pluralize: 8.0.0
|
3674
|
-
typescript: 5.8.
|
3674
|
+
typescript: 5.8.3
|
3675
3675
|
transitivePeerDependencies:
|
3676
3676
|
- chokidar
|
3677
3677
|
|
@@ -3901,32 +3901,32 @@ snapshots:
|
|
3901
3901
|
'@types/methods': 1.1.4
|
3902
3902
|
'@types/superagent': 8.1.8
|
3903
3903
|
|
3904
|
-
'@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.
|
3904
|
+
'@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.3))(eslint@9.23.0)(typescript@5.8.3)':
|
3905
3905
|
dependencies:
|
3906
3906
|
'@eslint-community/regexpp': 4.12.1
|
3907
|
-
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
3907
|
+
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
3908
3908
|
'@typescript-eslint/scope-manager': 8.28.0
|
3909
|
-
'@typescript-eslint/type-utils': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
3910
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
3909
|
+
'@typescript-eslint/type-utils': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
3910
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
3911
3911
|
'@typescript-eslint/visitor-keys': 8.28.0
|
3912
3912
|
eslint: 9.23.0
|
3913
3913
|
graphemer: 1.4.0
|
3914
3914
|
ignore: 5.3.1
|
3915
3915
|
natural-compare: 1.4.0
|
3916
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
3917
|
-
typescript: 5.8.
|
3916
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
3917
|
+
typescript: 5.8.3
|
3918
3918
|
transitivePeerDependencies:
|
3919
3919
|
- supports-color
|
3920
3920
|
|
3921
|
-
'@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.
|
3921
|
+
'@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.3)':
|
3922
3922
|
dependencies:
|
3923
3923
|
'@typescript-eslint/scope-manager': 8.28.0
|
3924
3924
|
'@typescript-eslint/types': 8.28.0
|
3925
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
3925
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
3926
3926
|
'@typescript-eslint/visitor-keys': 8.28.0
|
3927
3927
|
debug: 4.4.0
|
3928
3928
|
eslint: 9.23.0
|
3929
|
-
typescript: 5.8.
|
3929
|
+
typescript: 5.8.3
|
3930
3930
|
transitivePeerDependencies:
|
3931
3931
|
- supports-color
|
3932
3932
|
|
@@ -3935,20 +3935,20 @@ snapshots:
|
|
3935
3935
|
'@typescript-eslint/types': 8.28.0
|
3936
3936
|
'@typescript-eslint/visitor-keys': 8.28.0
|
3937
3937
|
|
3938
|
-
'@typescript-eslint/type-utils@8.28.0(eslint@9.23.0)(typescript@5.8.
|
3938
|
+
'@typescript-eslint/type-utils@8.28.0(eslint@9.23.0)(typescript@5.8.3)':
|
3939
3939
|
dependencies:
|
3940
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
3941
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
3940
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
3941
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
3942
3942
|
debug: 4.4.0
|
3943
3943
|
eslint: 9.23.0
|
3944
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
3945
|
-
typescript: 5.8.
|
3944
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
3945
|
+
typescript: 5.8.3
|
3946
3946
|
transitivePeerDependencies:
|
3947
3947
|
- supports-color
|
3948
3948
|
|
3949
3949
|
'@typescript-eslint/types@8.28.0': {}
|
3950
3950
|
|
3951
|
-
'@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.
|
3951
|
+
'@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.3)':
|
3952
3952
|
dependencies:
|
3953
3953
|
'@typescript-eslint/types': 8.28.0
|
3954
3954
|
'@typescript-eslint/visitor-keys': 8.28.0
|
@@ -3957,19 +3957,19 @@ snapshots:
|
|
3957
3957
|
is-glob: 4.0.3
|
3958
3958
|
minimatch: 9.0.5
|
3959
3959
|
semver: 7.6.3
|
3960
|
-
ts-api-utils: 2.1.0(typescript@5.8.
|
3961
|
-
typescript: 5.8.
|
3960
|
+
ts-api-utils: 2.1.0(typescript@5.8.3)
|
3961
|
+
typescript: 5.8.3
|
3962
3962
|
transitivePeerDependencies:
|
3963
3963
|
- supports-color
|
3964
3964
|
|
3965
|
-
'@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.
|
3965
|
+
'@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.3)':
|
3966
3966
|
dependencies:
|
3967
3967
|
'@eslint-community/eslint-utils': 4.4.0(eslint@9.23.0)
|
3968
3968
|
'@typescript-eslint/scope-manager': 8.28.0
|
3969
3969
|
'@typescript-eslint/types': 8.28.0
|
3970
|
-
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.
|
3970
|
+
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.3)
|
3971
3971
|
eslint: 9.23.0
|
3972
|
-
typescript: 5.8.
|
3972
|
+
typescript: 5.8.3
|
3973
3973
|
transitivePeerDependencies:
|
3974
3974
|
- supports-color
|
3975
3975
|
|
@@ -3997,12 +3997,12 @@ snapshots:
|
|
3997
3997
|
transitivePeerDependencies:
|
3998
3998
|
- supports-color
|
3999
3999
|
|
4000
|
-
'@vitest/eslint-plugin@1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.
|
4000
|
+
'@vitest/eslint-plugin@1.1.38(@typescript-eslint/utils@8.28.0(eslint@9.23.0)(typescript@5.8.3))(eslint@9.23.0)(typescript@5.8.3)(vitest@1.6.1(@types/node@22.13.14)(terser@5.31.3))':
|
4001
4001
|
dependencies:
|
4002
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
4002
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
4003
4003
|
eslint: 9.23.0
|
4004
4004
|
optionalDependencies:
|
4005
|
-
typescript: 5.8.
|
4005
|
+
typescript: 5.8.3
|
4006
4006
|
vitest: 1.6.1(@types/node@22.13.14)(terser@5.31.3)
|
4007
4007
|
|
4008
4008
|
'@vitest/expect@1.6.1':
|
@@ -6261,9 +6261,9 @@ snapshots:
|
|
6261
6261
|
|
6262
6262
|
tree-kill@1.2.2: {}
|
6263
6263
|
|
6264
|
-
ts-api-utils@2.1.0(typescript@5.8.
|
6264
|
+
ts-api-utils@2.1.0(typescript@5.8.3):
|
6265
6265
|
dependencies:
|
6266
|
-
typescript: 5.8.
|
6266
|
+
typescript: 5.8.3
|
6267
6267
|
|
6268
6268
|
ts-deepmerge@6.2.1: {}
|
6269
6269
|
|
@@ -6302,19 +6302,19 @@ snapshots:
|
|
6302
6302
|
|
6303
6303
|
typedarray@0.0.6: {}
|
6304
6304
|
|
6305
|
-
typescript-eslint@8.28.0(eslint@9.23.0)(typescript@5.8.
|
6305
|
+
typescript-eslint@8.28.0(eslint@9.23.0)(typescript@5.8.3):
|
6306
6306
|
dependencies:
|
6307
|
-
'@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.
|
6308
|
-
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
6309
|
-
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.
|
6307
|
+
'@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0)(typescript@5.8.3))(eslint@9.23.0)(typescript@5.8.3)
|
6308
|
+
'@typescript-eslint/parser': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
6309
|
+
'@typescript-eslint/utils': 8.28.0(eslint@9.23.0)(typescript@5.8.3)
|
6310
6310
|
eslint: 9.23.0
|
6311
|
-
typescript: 5.8.
|
6311
|
+
typescript: 5.8.3
|
6312
6312
|
transitivePeerDependencies:
|
6313
6313
|
- supports-color
|
6314
6314
|
|
6315
6315
|
typescript@5.7.2: {}
|
6316
6316
|
|
6317
|
-
typescript@5.8.
|
6317
|
+
typescript@5.8.3: {}
|
6318
6318
|
|
6319
6319
|
ufo@1.5.4: {}
|
6320
6320
|
|