@zuplo/cli 1.24.0 → 1.25.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 ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extension": ["js"],
3
+ "spec": "dist/**/*.spec.js",
4
+ "enable-source-maps": true,
5
+ "timeout": 5000
6
+ }
package/README.md CHANGED
@@ -4,10 +4,11 @@
4
4
  zup <command>
5
5
 
6
6
  Commands:
7
- zup delete Deletes the zup at the URL
8
- zup deploy Deploys current Git branch of the current directory.
9
- zup list Lists all deployed zups
10
- zup test Runs the tests under /tests against an endpoint
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-regex",
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-regex",
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-regex",
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-regex",
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-regex",
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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routes.generated.js.map
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@zuplo/cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.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
- "prettier": "^2.7.1",
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
  },