@zuplo/cli 1.24.0 → 1.26.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/.mocharc.json +6 -0
- package/README.md +5 -4
- package/dist/cli.js +2 -0
- package/dist/cmds/convert.js +21 -0
- package/dist/convert/__tests__/engine.spec.js +303 -0
- package/dist/convert/engine.js +53 -0
- package/dist/convert/handler.js +42 -0
- package/dist/convert/routes.generated.js +2 -0
- package/package.json +10 -3
package/.mocharc.json
ADDED
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
zup <command>
|
|
5
5
|
|
|
6
6
|
Commands:
|
|
7
|
-
zup
|
|
8
|
-
zup
|
|
9
|
-
zup
|
|
10
|
-
zup
|
|
7
|
+
zup convert Converts routes.json to routes.oas.json
|
|
8
|
+
zup delete Deletes the zup at the URL
|
|
9
|
+
zup deploy Deploys current Git branch of the current directory
|
|
10
|
+
zup list Lists all deployed zups
|
|
11
|
+
zup test Runs the tests under /tests against an endpoint
|
|
11
12
|
|
|
12
13
|
Options:
|
|
13
14
|
--version Show version number [boolean]
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ dotenv.config();
|
|
|
3
3
|
import { gte } from "semver";
|
|
4
4
|
import { hideBin } from "yargs/helpers";
|
|
5
5
|
import yargs from "yargs/yargs";
|
|
6
|
+
import convert from "./cmds/convert.js";
|
|
6
7
|
import deleteZup from "./cmds/delete.js";
|
|
7
8
|
import deploy from "./cmds/deploy.js";
|
|
8
9
|
import list from "./cmds/list.js";
|
|
@@ -12,6 +13,7 @@ const MIN_NODE_VERSION = "18.0.0";
|
|
|
12
13
|
if (gte(process.versions.node, MIN_NODE_VERSION)) {
|
|
13
14
|
void yargs(hideBin(process.argv))
|
|
14
15
|
.env("ZUPLO")
|
|
16
|
+
.command(convert)
|
|
15
17
|
.command(deleteZup)
|
|
16
18
|
.command(deploy)
|
|
17
19
|
.command(list)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import setBlocking from "../common/output.js";
|
|
2
|
+
import { convert } from "../convert/handler.js";
|
|
3
|
+
export default {
|
|
4
|
+
desc: "Converts routes.json to routes.oas.json",
|
|
5
|
+
command: "convert",
|
|
6
|
+
builder: (yargs) => {
|
|
7
|
+
return yargs
|
|
8
|
+
.option("dir", {
|
|
9
|
+
type: "string",
|
|
10
|
+
describe: "The directory containing your config/routes.json",
|
|
11
|
+
default: ".",
|
|
12
|
+
normalize: true,
|
|
13
|
+
hidden: true,
|
|
14
|
+
})
|
|
15
|
+
.middleware([setBlocking]);
|
|
16
|
+
},
|
|
17
|
+
handler: async (argv) => {
|
|
18
|
+
await convert(argv);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { convertRoutes } from "../engine.js";
|
|
3
|
+
describe("Routes.json Conversion Engine", () => {
|
|
4
|
+
it("should handle simple route conversion (no version)", () => {
|
|
5
|
+
const routesConfig = {
|
|
6
|
+
versions: [
|
|
7
|
+
{
|
|
8
|
+
name: "v1",
|
|
9
|
+
pathPrefix: "/v1",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "none",
|
|
13
|
+
pathPrefix: "",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
routes: [
|
|
17
|
+
{
|
|
18
|
+
methods: ["GET"],
|
|
19
|
+
path: "/test",
|
|
20
|
+
summary: "Test route",
|
|
21
|
+
version: "none",
|
|
22
|
+
corsPolicy: "anything-goes",
|
|
23
|
+
description: "This is a test route",
|
|
24
|
+
operationId: "random",
|
|
25
|
+
handler: {
|
|
26
|
+
export: "urlRewriteHandler",
|
|
27
|
+
module: "$import(@zuplo/runtime)",
|
|
28
|
+
options: {
|
|
29
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
30
|
+
forwardSearch: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
const expected = {
|
|
37
|
+
openapi: "3.1.0",
|
|
38
|
+
info: {
|
|
39
|
+
title: "Converted from config/routes.json",
|
|
40
|
+
version: "1.0.0",
|
|
41
|
+
},
|
|
42
|
+
paths: {
|
|
43
|
+
"/test": {
|
|
44
|
+
get: {
|
|
45
|
+
summary: "Test route",
|
|
46
|
+
description: "This is a test route",
|
|
47
|
+
operationId: "random",
|
|
48
|
+
"x-zuplo-route": {
|
|
49
|
+
corsPolicy: "anything-goes",
|
|
50
|
+
handler: {
|
|
51
|
+
export: "urlRewriteHandler",
|
|
52
|
+
module: "$import(@zuplo/runtime)",
|
|
53
|
+
options: {
|
|
54
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
55
|
+
forwardSearch: true,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
version: "none",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
"x-zuplo-path": {
|
|
62
|
+
pathMode: "path-to-regexp",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const actual = convertRoutes(routesConfig);
|
|
68
|
+
expect(actual).to.deep.equal(expected);
|
|
69
|
+
});
|
|
70
|
+
it("should handle simple route conversion (with version)", () => {
|
|
71
|
+
const routesConfig = {
|
|
72
|
+
versions: [
|
|
73
|
+
{
|
|
74
|
+
name: "v1",
|
|
75
|
+
pathPrefix: "/v1",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "none",
|
|
79
|
+
pathPrefix: "",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
routes: [
|
|
83
|
+
{
|
|
84
|
+
methods: ["GET"],
|
|
85
|
+
path: "/test",
|
|
86
|
+
summary: "Test route",
|
|
87
|
+
version: "v1",
|
|
88
|
+
corsPolicy: "anything-goes",
|
|
89
|
+
description: "This is a test route",
|
|
90
|
+
operationId: "random",
|
|
91
|
+
handler: {
|
|
92
|
+
export: "urlRewriteHandler",
|
|
93
|
+
module: "$import(@zuplo/runtime)",
|
|
94
|
+
options: {
|
|
95
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
96
|
+
forwardSearch: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
const expected = {
|
|
103
|
+
openapi: "3.1.0",
|
|
104
|
+
info: {
|
|
105
|
+
title: "Converted from config/routes.json",
|
|
106
|
+
version: "1.0.0",
|
|
107
|
+
},
|
|
108
|
+
paths: {
|
|
109
|
+
"/v1/test": {
|
|
110
|
+
get: {
|
|
111
|
+
summary: "Test route",
|
|
112
|
+
description: "This is a test route",
|
|
113
|
+
operationId: "random",
|
|
114
|
+
"x-zuplo-route": {
|
|
115
|
+
corsPolicy: "anything-goes",
|
|
116
|
+
handler: {
|
|
117
|
+
export: "urlRewriteHandler",
|
|
118
|
+
module: "$import(@zuplo/runtime)",
|
|
119
|
+
options: {
|
|
120
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
121
|
+
forwardSearch: true,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
version: "none",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
"x-zuplo-path": {
|
|
128
|
+
pathMode: "path-to-regexp",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
const actual = convertRoutes(routesConfig);
|
|
134
|
+
expect(actual).to.deep.equal(expected);
|
|
135
|
+
});
|
|
136
|
+
it("should handle single route with multiple methods", () => {
|
|
137
|
+
const routesConfig = {
|
|
138
|
+
versions: [
|
|
139
|
+
{
|
|
140
|
+
name: "v1",
|
|
141
|
+
pathPrefix: "/v1",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "none",
|
|
145
|
+
pathPrefix: "",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
routes: [
|
|
149
|
+
{
|
|
150
|
+
methods: ["GET", "POST", "HEAD"],
|
|
151
|
+
path: "/test",
|
|
152
|
+
summary: "Test route",
|
|
153
|
+
version: "v1",
|
|
154
|
+
corsPolicy: "anything-goes",
|
|
155
|
+
description: "This is a test route",
|
|
156
|
+
operationId: "random",
|
|
157
|
+
handler: {
|
|
158
|
+
export: "urlRewriteHandler",
|
|
159
|
+
module: "$import(@zuplo/runtime)",
|
|
160
|
+
options: {
|
|
161
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
162
|
+
forwardSearch: true,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
const expected = {
|
|
169
|
+
openapi: "3.1.0",
|
|
170
|
+
info: {
|
|
171
|
+
title: "Converted from config/routes.json",
|
|
172
|
+
version: "1.0.0",
|
|
173
|
+
},
|
|
174
|
+
paths: {
|
|
175
|
+
"/v1/test": {
|
|
176
|
+
"get,post,head": {
|
|
177
|
+
summary: "Test route",
|
|
178
|
+
description: "This is a test route",
|
|
179
|
+
operationId: "random",
|
|
180
|
+
"x-zuplo-route": {
|
|
181
|
+
corsPolicy: "anything-goes",
|
|
182
|
+
handler: {
|
|
183
|
+
export: "urlRewriteHandler",
|
|
184
|
+
module: "$import(@zuplo/runtime)",
|
|
185
|
+
options: {
|
|
186
|
+
rewritePattern: "https://jsonplaceholder.typicode.com/todos",
|
|
187
|
+
forwardSearch: true,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
version: "none",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
"x-zuplo-path": {
|
|
194
|
+
pathMode: "path-to-regexp",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
const actual = convertRoutes(routesConfig);
|
|
200
|
+
expect(actual).to.deep.equal(expected);
|
|
201
|
+
});
|
|
202
|
+
it("should handle single route with different methods", () => {
|
|
203
|
+
const routesConfig = {
|
|
204
|
+
versions: [
|
|
205
|
+
{
|
|
206
|
+
name: "v1",
|
|
207
|
+
pathPrefix: "/v1",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "none",
|
|
211
|
+
pathPrefix: "",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
routes: [
|
|
215
|
+
{
|
|
216
|
+
methods: ["GET"],
|
|
217
|
+
path: "/test",
|
|
218
|
+
summary: "Test route",
|
|
219
|
+
description: "This is a test route",
|
|
220
|
+
version: "v1",
|
|
221
|
+
corsPolicy: "anything-goes",
|
|
222
|
+
handler: {
|
|
223
|
+
export: "urlRewriteHandler",
|
|
224
|
+
module: "$import(@zuplo/runtime)",
|
|
225
|
+
options: {
|
|
226
|
+
rewritePattern: "https://welcome.zuplo.io/",
|
|
227
|
+
forwardSearch: true,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
operationId: "random1",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
methods: ["POST"],
|
|
234
|
+
path: "/test",
|
|
235
|
+
summary: "Test route",
|
|
236
|
+
description: "This is a test route",
|
|
237
|
+
version: "v1",
|
|
238
|
+
corsPolicy: "anything-goes",
|
|
239
|
+
handler: {
|
|
240
|
+
export: "urlRewriteHandler",
|
|
241
|
+
module: "$import(@zuplo/runtime)",
|
|
242
|
+
options: {
|
|
243
|
+
rewritePattern: "https://welcome.zuplo.io/",
|
|
244
|
+
forwardSearch: true,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
operationId: "random2",
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
const expected = {
|
|
252
|
+
openapi: "3.1.0",
|
|
253
|
+
info: {
|
|
254
|
+
title: "Converted from config/routes.json",
|
|
255
|
+
version: "1.0.0",
|
|
256
|
+
},
|
|
257
|
+
paths: {
|
|
258
|
+
"/v1/test": {
|
|
259
|
+
get: {
|
|
260
|
+
summary: "Test route",
|
|
261
|
+
description: "This is a test route",
|
|
262
|
+
operationId: "random1",
|
|
263
|
+
"x-zuplo-route": {
|
|
264
|
+
corsPolicy: "anything-goes",
|
|
265
|
+
handler: {
|
|
266
|
+
export: "urlRewriteHandler",
|
|
267
|
+
module: "$import(@zuplo/runtime)",
|
|
268
|
+
options: {
|
|
269
|
+
rewritePattern: "https://welcome.zuplo.io/",
|
|
270
|
+
forwardSearch: true,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
version: "none",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
post: {
|
|
277
|
+
summary: "Test route",
|
|
278
|
+
description: "This is a test route",
|
|
279
|
+
operationId: "random2",
|
|
280
|
+
"x-zuplo-route": {
|
|
281
|
+
corsPolicy: "anything-goes",
|
|
282
|
+
handler: {
|
|
283
|
+
export: "urlRewriteHandler",
|
|
284
|
+
module: "$import(@zuplo/runtime)",
|
|
285
|
+
options: {
|
|
286
|
+
rewritePattern: "https://welcome.zuplo.io/",
|
|
287
|
+
forwardSearch: true,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
version: "none",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
"x-zuplo-path": {
|
|
294
|
+
pathMode: "path-to-regexp",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const actual = convertRoutes(routesConfig);
|
|
300
|
+
expect(actual).to.deep.equal(expected);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
//# sourceMappingURL=engine.spec.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
export function convertRoutes(routesConfig) {
|
|
3
|
+
const base = generateBaseOpenAPIConfig();
|
|
4
|
+
const paths = base.paths;
|
|
5
|
+
routesConfig.routes.forEach((route) => {
|
|
6
|
+
const pathObjectKey = fullPath(route, routesConfig);
|
|
7
|
+
let pathObject;
|
|
8
|
+
if (!paths[pathObjectKey]) {
|
|
9
|
+
pathObject = {};
|
|
10
|
+
pathObject["x-zuplo-path"] = {
|
|
11
|
+
pathMode: "path-to-regexp",
|
|
12
|
+
};
|
|
13
|
+
paths[pathObjectKey] = pathObject;
|
|
14
|
+
}
|
|
15
|
+
pathObject = paths[pathObjectKey];
|
|
16
|
+
const operationKey = route.methods.map((m) => m.toLowerCase()).join(",");
|
|
17
|
+
const operation = {};
|
|
18
|
+
operation["summary"] = route.summary;
|
|
19
|
+
operation["description"] = route.description;
|
|
20
|
+
operation["operationId"] = route.operationId;
|
|
21
|
+
const xZuploRoute = {};
|
|
22
|
+
xZuploRoute["version"] = "none";
|
|
23
|
+
if (route.corsPolicy)
|
|
24
|
+
xZuploRoute["corsPolicy"] = route.corsPolicy;
|
|
25
|
+
if (route.custom)
|
|
26
|
+
xZuploRoute["custom"] = route.custom;
|
|
27
|
+
if (route.policies)
|
|
28
|
+
xZuploRoute["policies"] = route.policies;
|
|
29
|
+
if (route.key)
|
|
30
|
+
xZuploRoute["key"] = route.key;
|
|
31
|
+
if (route.label)
|
|
32
|
+
xZuploRoute["label"] = route.label;
|
|
33
|
+
xZuploRoute["handler"] = route.handler;
|
|
34
|
+
operation["x-zuplo-route"] = xZuploRoute;
|
|
35
|
+
pathObject[operationKey] = operation;
|
|
36
|
+
});
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
function generateBaseOpenAPIConfig() {
|
|
40
|
+
return {
|
|
41
|
+
openapi: "3.1.0",
|
|
42
|
+
info: {
|
|
43
|
+
title: "Converted from config/routes.json",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
},
|
|
46
|
+
paths: {},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function fullPath(path, context) {
|
|
50
|
+
const versionPrefix = context.versions.filter((v) => v.name === path.version)[0].pathPrefix;
|
|
51
|
+
return join(versionPrefix, path.path);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import prettier from "prettier";
|
|
5
|
+
import { logger } from "../common/logger.js";
|
|
6
|
+
import { printCriticalFailureToConsoleAndExit } from "../common/output.js";
|
|
7
|
+
import { convertRoutes } from "./engine.js";
|
|
8
|
+
export async function convert(argv) {
|
|
9
|
+
try {
|
|
10
|
+
const dir = argv.dir;
|
|
11
|
+
const normalizedDir = join(relative(process.cwd(), dir));
|
|
12
|
+
const rawRoutes = await readFile(join(normalizedDir, "config", "routes.json"));
|
|
13
|
+
const routes = JSON.parse(rawRoutes.toString());
|
|
14
|
+
const openApi = convertRoutes(routes);
|
|
15
|
+
const openAPIFilePath = join(normalizedDir, "config", "routes.oas.json");
|
|
16
|
+
const formattedOpenAPI = prettier.format(JSON.stringify(openApi), {
|
|
17
|
+
parser: "json-stringify",
|
|
18
|
+
});
|
|
19
|
+
writeFileSync(openAPIFilePath, formattedOpenAPI, {
|
|
20
|
+
flag: "w",
|
|
21
|
+
});
|
|
22
|
+
const policies = routes.policies;
|
|
23
|
+
const corsPolicies = routes.corsPolicies;
|
|
24
|
+
if (policies) {
|
|
25
|
+
const policiesFilePath = join(normalizedDir, "config", "policies.json");
|
|
26
|
+
const formattedPolicies = prettier.format(JSON.stringify({
|
|
27
|
+
policies: [...policies],
|
|
28
|
+
corsPolicies: corsPolicies ? [...corsPolicies] : [],
|
|
29
|
+
}, null, 2), {
|
|
30
|
+
parser: "json-stringify",
|
|
31
|
+
});
|
|
32
|
+
writeFileSync(policiesFilePath, formattedPolicies, {
|
|
33
|
+
flag: "w",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
logger.error(err);
|
|
39
|
+
printCriticalFailureToConsoleAndExit(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=handler.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuplo/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": "https://github.com/zuplo/cli",
|
|
6
6
|
"author": "Zuplo, Inc.",
|
|
7
7
|
"license": "Copyright 2022",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc --build"
|
|
9
|
+
"build": "tsc --build",
|
|
10
|
+
"test": "mocha",
|
|
11
|
+
"test:debug": "mocha --timeout 0"
|
|
10
12
|
},
|
|
11
13
|
"engines": {
|
|
12
14
|
"node": ">=18.0.0"
|
|
@@ -23,7 +25,10 @@
|
|
|
23
25
|
]
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
28
|
+
"@types/chai": "^4.3.4",
|
|
29
|
+
"@types/mocha": "^10.0.1",
|
|
26
30
|
"@types/node": "^18.11.5",
|
|
31
|
+
"@types/prettier": "^2.7.2",
|
|
27
32
|
"@types/rimraf": "^3.0.2",
|
|
28
33
|
"@types/semver": "^7.3.13",
|
|
29
34
|
"@types/tar": "^6.1.3",
|
|
@@ -31,6 +36,7 @@
|
|
|
31
36
|
"@types/yargs": "^17.0.13",
|
|
32
37
|
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
|
33
38
|
"@typescript-eslint/parser": "^5.42.0",
|
|
39
|
+
"chai": "^4.3.7",
|
|
34
40
|
"eslint": "^8.26.0",
|
|
35
41
|
"eslint-config-prettier": "^8.5.0",
|
|
36
42
|
"eslint-plugin-import": "^2.26.0",
|
|
@@ -38,7 +44,8 @@
|
|
|
38
44
|
"eslint-plugin-unicorn": "^44.0.2",
|
|
39
45
|
"husky": "^8.0.1",
|
|
40
46
|
"lint-staged": "^13.0.3",
|
|
41
|
-
"
|
|
47
|
+
"mocha": "^10.2.0",
|
|
48
|
+
"prettier": "^2.8.4",
|
|
42
49
|
"prettier-plugin-organize-imports": "^3.1.1",
|
|
43
50
|
"typescript": "^4.8.4"
|
|
44
51
|
},
|