counterfact 2.4.0 → 2.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 +1 -1
- package/bin/README.md +3 -2
- package/bin/counterfact.js +63 -0
- package/dist/repl/{RawHttpClient.js → raw-http-client.js} +7 -1
- package/dist/repl/repl.js +1 -1
- package/dist/server/counterfact-types/index.ts +1 -1
- package/dist/server/koa-middleware.js +3 -3
- package/dist/server/module-loader.js +33 -2
- package/dist/server/uncached-require.cjs +7 -3
- package/dist/typescript-generator/operation-type-coder.js +55 -1
- package/dist/typescript-generator/response-type-coder.js +1 -1
- package/dist/typescript-generator/script.js +3 -2
- package/package.json +4 -8
- /package/dist/counterfact-types/{OpenApiHeader.js → open-api-header.js} +0 -0
- /package/dist/server/counterfact-types/{OpenApiHeader.ts → open-api-header.ts} +0 -0
package/README.md
CHANGED
|
@@ -206,7 +206,7 @@ npx counterfact@latest [openapi.yaml] [destination] [options]
|
|
|
206
206
|
| `--proxy-url <url>` | Forward all requests to this URL by default |
|
|
207
207
|
| `--prefix <path>` | Base path prefix (e.g. `/api/v1`) |
|
|
208
208
|
|
|
209
|
-
Run `npx counterfact --help` for the full list of options.
|
|
209
|
+
Run `npx counterfact@latest --help` for the full list of options.
|
|
210
210
|
|
|
211
211
|
---
|
|
212
212
|
|
package/bin/README.md
CHANGED
|
@@ -11,7 +11,7 @@ This directory contains the executable script that is run when a developer invok
|
|
|
11
11
|
## How It Works
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
npx counterfact openapi.yaml ./api [options]
|
|
14
|
+
npx counterfact@latest openapi.yaml ./api [options]
|
|
15
15
|
│
|
|
16
16
|
▼
|
|
17
17
|
┌────────────────────────────┐
|
|
@@ -40,5 +40,6 @@ npx counterfact openapi.yaml ./api [options]
|
|
|
40
40
|
| `--spec <path>` | Path or URL to the OpenAPI document (alternative to positional argument) |
|
|
41
41
|
| `--proxy-url <url>` | Forward all unmatched requests to this upstream URL |
|
|
42
42
|
| `--prefix <path>` | Base path prefix for all routes (e.g. `/api/v1`) |
|
|
43
|
+
| `--no-update-check` | Disable the npm update check on startup |
|
|
43
44
|
|
|
44
|
-
Run `npx counterfact --help` to see the full option list.
|
|
45
|
+
Run `npx counterfact@latest --help` to see the full option list.
|
package/bin/counterfact.js
CHANGED
|
@@ -23,6 +23,18 @@ if (Number.parseInt(process.versions.node.split("."), 10) < MIN_NODE_VERSION) {
|
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const packageJson = JSON.parse(
|
|
27
|
+
await readFile(
|
|
28
|
+
nodePath.join(
|
|
29
|
+
nodePath.dirname(fileURLToPath(import.meta.url)),
|
|
30
|
+
"../package.json",
|
|
31
|
+
),
|
|
32
|
+
"utf8",
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const CURRENT_VERSION = packageJson.version;
|
|
37
|
+
|
|
26
38
|
const taglinesFile = await readFile(
|
|
27
39
|
nodePath.join(
|
|
28
40
|
nodePath.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -37,6 +49,48 @@ const DEFAULT_PORT = 3100;
|
|
|
37
49
|
|
|
38
50
|
const debug = createDebug("counterfact:bin:counterfact");
|
|
39
51
|
|
|
52
|
+
function isOutdated(current, latest) {
|
|
53
|
+
const [cMajor, cMinor, cPatch] = current.split(".").map(Number);
|
|
54
|
+
const [lMajor, lMinor, lPatch] = latest.split(".").map(Number);
|
|
55
|
+
|
|
56
|
+
if (lMajor > cMajor) return true;
|
|
57
|
+
if (lMajor === cMajor && lMinor > cMinor) return true;
|
|
58
|
+
if (lMajor === cMajor && lMinor === cMinor && lPatch > cPatch) return true;
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function checkForUpdates(currentVersion) {
|
|
64
|
+
if (process.env.CI) {
|
|
65
|
+
debug("skipping update check in CI environment");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(
|
|
71
|
+
"https://registry.npmjs.org/counterfact/latest",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
debug("update check failed with status %d", response.status);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
const latestVersion = data.version;
|
|
81
|
+
|
|
82
|
+
if (isOutdated(currentVersion, latestVersion)) {
|
|
83
|
+
process.stdout.write(
|
|
84
|
+
`\n⚠️ You're running counterfact ${currentVersion}\n`,
|
|
85
|
+
);
|
|
86
|
+
process.stdout.write(` Latest version is ${latestVersion}\n`);
|
|
87
|
+
process.stdout.write(` Run: npx counterfact@latest\n`);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
debug("update check error: %o", error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
40
94
|
debug("running ./bin/counterfact.js");
|
|
41
95
|
|
|
42
96
|
function padTagLine(tagLine) {
|
|
@@ -102,6 +156,11 @@ async function main(source, destination) {
|
|
|
102
156
|
|
|
103
157
|
const options = program.opts();
|
|
104
158
|
|
|
159
|
+
const updateCheckPromise =
|
|
160
|
+
options.updateCheck === false
|
|
161
|
+
? Promise.resolve()
|
|
162
|
+
: checkForUpdates(CURRENT_VERSION);
|
|
163
|
+
|
|
105
164
|
// --spec takes precedence over the positional [openapi.yaml] argument.
|
|
106
165
|
// When --spec is provided, the [openapi.yaml] positional slot shifts to
|
|
107
166
|
// become the [destination] argument (so `counterfact --spec api.yaml ./api`
|
|
@@ -226,6 +285,7 @@ async function main(source, destination) {
|
|
|
226
285
|
String.raw` |___ [__] |__| |\| | |=== |--< |--- |--| |___ | `,
|
|
227
286
|
" " + padTagLine(taglines[Math.floor(Math.random() * taglines.length)]),
|
|
228
287
|
"",
|
|
288
|
+
` Version ${CURRENT_VERSION}`,
|
|
229
289
|
` API Base URL ${url}`,
|
|
230
290
|
source === "_" ? undefined : ` Swagger UI ${swaggerUrl}`,
|
|
231
291
|
"",
|
|
@@ -254,6 +314,8 @@ async function main(source, destination) {
|
|
|
254
314
|
await start(config);
|
|
255
315
|
debug("started server");
|
|
256
316
|
|
|
317
|
+
await updateCheckPromise;
|
|
318
|
+
|
|
257
319
|
if (config.startRepl) {
|
|
258
320
|
startRepl();
|
|
259
321
|
}
|
|
@@ -346,5 +408,6 @@ program
|
|
|
346
408
|
"--spec <string>",
|
|
347
409
|
"path or URL to OpenAPI document (alternative to the positional [openapi.yaml] argument)",
|
|
348
410
|
)
|
|
411
|
+
.option("--no-update-check", "disable the npm update check on startup")
|
|
349
412
|
.action(main)
|
|
350
413
|
.parse(process.argv);
|
|
@@ -89,6 +89,12 @@ export class RawHttpClient {
|
|
|
89
89
|
#send(method, path, bodyAsStringOrObject, headers) {
|
|
90
90
|
const requestNumber = ++this.requestNumber;
|
|
91
91
|
const body = stringifyBody(bodyAsStringOrObject);
|
|
92
|
+
const effectiveHeaders = { ...headers };
|
|
93
|
+
if (typeof bodyAsStringOrObject === "object" &&
|
|
94
|
+
bodyAsStringOrObject !== null &&
|
|
95
|
+
!Object.keys(effectiveHeaders).some((k) => k.toLowerCase() === "content-type")) {
|
|
96
|
+
effectiveHeaders["Content-Type"] = "application/json";
|
|
97
|
+
}
|
|
92
98
|
return new Promise((resolve, reject) => {
|
|
93
99
|
const socket = net.createConnection({ host: this.host, port: this.port }, () => {
|
|
94
100
|
let request = `${method} ${path} HTTP/1.1\r\n`;
|
|
@@ -97,7 +103,7 @@ export class RawHttpClient {
|
|
|
97
103
|
if (body != null) {
|
|
98
104
|
request += `Content-Length: ${Buffer.byteLength(body)}\r\n`;
|
|
99
105
|
}
|
|
100
|
-
for (const [key, value] of Object.entries(
|
|
106
|
+
for (const [key, value] of Object.entries(effectiveHeaders)) {
|
|
101
107
|
request += `${key}: ${value}\r\n`;
|
|
102
108
|
}
|
|
103
109
|
request += `\r\n`;
|
package/dist/repl/repl.js
CHANGED
|
@@ -19,10 +19,10 @@ const HEADERS_TO_DROP = new Set([
|
|
|
19
19
|
"trailer",
|
|
20
20
|
"trailers",
|
|
21
21
|
]);
|
|
22
|
-
function addCors(ctx, headers) {
|
|
22
|
+
function addCors(ctx, allowedMethods, headers) {
|
|
23
23
|
// Always append CORS headers, reflecting back the headers requested if any
|
|
24
24
|
ctx.set("Access-Control-Allow-Origin", headers?.origin ?? "*");
|
|
25
|
-
ctx.set("Access-Control-Allow-Methods",
|
|
25
|
+
ctx.set("Access-Control-Allow-Methods", allowedMethods);
|
|
26
26
|
ctx.set("Access-Control-Allow-Headers", headers?.["access-control-request-headers"] ?? []);
|
|
27
27
|
ctx.set("Access-Control-Expose-Headers", headers?.["access-control-request-headers"] ?? []);
|
|
28
28
|
ctx.set("Access-Control-Allow-Credentials", "true");
|
|
@@ -55,7 +55,7 @@ export function koaMiddleware(dispatcher, config, proxy = koaProxy) {
|
|
|
55
55
|
if (isProxyEnabledForPath(path, config) && proxyUrl) {
|
|
56
56
|
return proxy("/", { changeOrigin: true, target: proxyUrl })(ctx, next);
|
|
57
57
|
}
|
|
58
|
-
addCors(ctx, headers);
|
|
58
|
+
addCors(ctx, dispatcher.registry.allowedMethods(path), headers);
|
|
59
59
|
if (method === "OPTIONS") {
|
|
60
60
|
ctx.status = HTTP_STATUS_CODE_OK;
|
|
61
61
|
return undefined;
|
|
@@ -55,6 +55,7 @@ export class ModuleLoader extends EventTarget {
|
|
|
55
55
|
if (eventName === "unlink") {
|
|
56
56
|
this.registry.remove(url);
|
|
57
57
|
this.dispatchEvent(new Event("remove"));
|
|
58
|
+
return;
|
|
58
59
|
}
|
|
59
60
|
const dependencies = this.dependencyGraph.dependentsOf(pathName);
|
|
60
61
|
void this.loadEndpoint(pathName);
|
|
@@ -102,9 +103,39 @@ export class ModuleLoader extends EventTarget {
|
|
|
102
103
|
const doImport = (await determineModuleKind(pathName)) === "commonjs"
|
|
103
104
|
? uncachedRequire
|
|
104
105
|
: uncachedImport;
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
let importError;
|
|
107
|
+
const endpoint = (await doImport(pathName).catch((error) => {
|
|
108
|
+
importError = error;
|
|
107
109
|
}));
|
|
110
|
+
if (importError !== undefined) {
|
|
111
|
+
const isSyntaxError = importError instanceof SyntaxError ||
|
|
112
|
+
String(importError).startsWith("SyntaxError:");
|
|
113
|
+
const displayPath = nodePath
|
|
114
|
+
.relative(process.cwd(), unescapePathForWindows(pathName))
|
|
115
|
+
.replaceAll("\\", "/");
|
|
116
|
+
const message = isSyntaxError
|
|
117
|
+
? `There is a syntax error in the route file: ${displayPath}`
|
|
118
|
+
: `There was an error loading the route file: ${displayPath}`;
|
|
119
|
+
const errorResponse = () => ({
|
|
120
|
+
body: message,
|
|
121
|
+
status: 500,
|
|
122
|
+
});
|
|
123
|
+
this.registry.add(url, {
|
|
124
|
+
DELETE: errorResponse,
|
|
125
|
+
GET: errorResponse,
|
|
126
|
+
HEAD: errorResponse,
|
|
127
|
+
OPTIONS: errorResponse,
|
|
128
|
+
PATCH: errorResponse,
|
|
129
|
+
POST: errorResponse,
|
|
130
|
+
PUT: errorResponse,
|
|
131
|
+
TRACE: errorResponse,
|
|
132
|
+
});
|
|
133
|
+
this.dispatchEvent(new Event("add"));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!endpoint) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
108
139
|
this.dispatchEvent(new Event("add"));
|
|
109
140
|
if (basename(pathName).startsWith("_.context.") &&
|
|
110
141
|
isContextModule(endpoint)) {
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
uncachedRequire: function uncachedRequire(moduleName) {
|
|
5
|
-
|
|
5
|
+
try {
|
|
6
|
+
delete require.cache[require.resolve(moduleName)];
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
// eslint-disable-next-line security/detect-non-literal-require
|
|
9
|
+
return Promise.resolve(require(moduleName));
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return Promise.reject(error);
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
};
|
|
@@ -6,6 +6,56 @@ import { ResponsesTypeCoder } from "./responses-type-coder.js";
|
|
|
6
6
|
import { SchemaTypeCoder } from "./schema-type-coder.js";
|
|
7
7
|
import { TypeCoder } from "./type-coder.js";
|
|
8
8
|
import { ParameterExportTypeCoder } from "./parameter-export-type-coder.js";
|
|
9
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
|
|
10
|
+
const RESERVED_WORDS = new Set([
|
|
11
|
+
"break",
|
|
12
|
+
"case",
|
|
13
|
+
"catch",
|
|
14
|
+
"class",
|
|
15
|
+
"const",
|
|
16
|
+
"continue",
|
|
17
|
+
"debugger",
|
|
18
|
+
"default",
|
|
19
|
+
"delete",
|
|
20
|
+
"do",
|
|
21
|
+
"else",
|
|
22
|
+
"export",
|
|
23
|
+
"extends",
|
|
24
|
+
"false",
|
|
25
|
+
"finally",
|
|
26
|
+
"for",
|
|
27
|
+
"function",
|
|
28
|
+
"if",
|
|
29
|
+
"import",
|
|
30
|
+
"in",
|
|
31
|
+
"instanceof",
|
|
32
|
+
"new",
|
|
33
|
+
"null",
|
|
34
|
+
"return",
|
|
35
|
+
"static",
|
|
36
|
+
"super",
|
|
37
|
+
"switch",
|
|
38
|
+
"this",
|
|
39
|
+
"throw",
|
|
40
|
+
"true",
|
|
41
|
+
"try",
|
|
42
|
+
"typeof",
|
|
43
|
+
"var",
|
|
44
|
+
"void",
|
|
45
|
+
"while",
|
|
46
|
+
"with",
|
|
47
|
+
"yield",
|
|
48
|
+
"await",
|
|
49
|
+
"enum",
|
|
50
|
+
"implements",
|
|
51
|
+
"interface",
|
|
52
|
+
"let",
|
|
53
|
+
"package",
|
|
54
|
+
"private",
|
|
55
|
+
"protected",
|
|
56
|
+
"public",
|
|
57
|
+
"type",
|
|
58
|
+
]);
|
|
9
59
|
function sanitizeIdentifier(value) {
|
|
10
60
|
// Treat any run of non-identifier characters as a camelCase separator
|
|
11
61
|
let result = value.replaceAll(/[^\w$]+(?<next>.)/gu, (_, char) => char.toUpperCase());
|
|
@@ -15,6 +65,10 @@ function sanitizeIdentifier(value) {
|
|
|
15
65
|
if (/^\d/u.test(result)) {
|
|
16
66
|
result = `_${result}`;
|
|
17
67
|
}
|
|
68
|
+
// If the identifier is a reserved word, append an underscore
|
|
69
|
+
if (RESERVED_WORDS.has(result)) {
|
|
70
|
+
result = `${result}_`;
|
|
71
|
+
}
|
|
18
72
|
return result || "_";
|
|
19
73
|
}
|
|
20
74
|
export class OperationTypeCoder extends TypeCoder {
|
|
@@ -56,7 +110,7 @@ export class OperationTypeCoder extends TypeCoder {
|
|
|
56
110
|
return response.get("content").map((content, contentType) => `{
|
|
57
111
|
status: ${status},
|
|
58
112
|
contentType?: "${contentType}",
|
|
59
|
-
body?: ${new SchemaTypeCoder(content.get("schema")).write(script)}
|
|
113
|
+
body?: ${content.has("schema") ? new SchemaTypeCoder(content.get("schema")).write(script) : "unknown"}
|
|
60
114
|
}`);
|
|
61
115
|
}
|
|
62
116
|
if (response.has("schema")) {
|
|
@@ -14,7 +14,7 @@ export class ResponseTypeCoder extends TypeCoder {
|
|
|
14
14
|
return response.get("content").map((content, mediaType) => [
|
|
15
15
|
mediaType,
|
|
16
16
|
`{
|
|
17
|
-
schema: ${new SchemaTypeCoder(content.get("schema")).write(script)}
|
|
17
|
+
schema: ${content.has("schema") ? new SchemaTypeCoder(content.get("schema")).write(script) : "unknown"}
|
|
18
18
|
}`,
|
|
19
19
|
]);
|
|
20
20
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import createDebugger from "debug";
|
|
3
3
|
import { format } from "prettier";
|
|
4
|
+
import { escapePathForWindows } from "../util/windows-escape.js";
|
|
4
5
|
const debug = createDebugger("counterfact:typescript-generator:script");
|
|
5
6
|
export class Script {
|
|
6
7
|
constructor(repository, path) {
|
|
@@ -117,9 +118,9 @@ export class Script {
|
|
|
117
118
|
}
|
|
118
119
|
importStatements() {
|
|
119
120
|
return Array.from(this.imports, ([name, { isDefault, isType, script }]) => {
|
|
120
|
-
const resolvedPath = nodePath
|
|
121
|
+
const resolvedPath = escapePathForWindows(nodePath
|
|
121
122
|
.relative(nodePath.dirname(this.path).replaceAll("\\", "/"), script.path.replace(/\.ts$/u, ".js"))
|
|
122
|
-
.replaceAll("\\", "/");
|
|
123
|
+
.replaceAll("\\", "/"));
|
|
123
124
|
return `import${isType ? " type" : ""} ${isDefault ? name : `{ ${name} }`} from "${resolvedPath.includes("../") ? "" : "./"}${resolvedPath}";`;
|
|
124
125
|
});
|
|
125
126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "counterfact",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Generate a TypeScript-based mock server from an OpenAPI spec in seconds — with stateful routes, hot reload, and REPL support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/app.js",
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"scripts": {
|
|
67
67
|
"test": "yarn node --experimental-vm-modules ./node_modules/jest-cli/bin/jest --testPathIgnorePatterns=black-box",
|
|
68
68
|
"test:black-box": "rimraf dist && yarn build && python3 -m pytest test-black-box/ -v",
|
|
69
|
-
"test:mutants": "stryker run stryker.config.json",
|
|
70
69
|
"test:tsd": "tsd --typings ./dist/server/counterfact-types/index.ts --files ./test/**/*.test-d.ts",
|
|
71
70
|
"build": "rm -rf dist && tsc && copyfiles -f \"src/client/**\" dist/client && copyfiles -f \"src/counterfact-types/*.ts\" dist/server/counterfact-types && copyfiles -f \"src/server/*.cjs\" dist/server",
|
|
72
71
|
"prepack": "yarn build",
|
|
73
72
|
"release": "npx changeset publish",
|
|
74
73
|
"prepare": "husky install",
|
|
75
74
|
"lint": "eslint . --plugin file-progress --rule 'file-progress/activate: 1' --ignore-pattern dist --ignore-pattern out --ignore-pattern coverage",
|
|
75
|
+
"lint:fix": "eslint --fix . --ignore-pattern dist --ignore-pattern out --ignore-pattern coverage",
|
|
76
76
|
"lint:quickfix": "eslint --fix . eslint --fix demo-ts --rule=\"import/namespace: 0,etc/no-deprecated:0,import/no-cycle:0,no-explicit-type-exports/no-explicit-type-exports:0,import/no-deprecated:0,import/no-self-import:0,import/default:0,import/no-named-as-default:0\" --ignore-pattern dist --ignore-pattern out",
|
|
77
77
|
"go:petstore": "yarn build && yarn counterfact https://petstore3.swagger.io/api/v3/openapi.json out",
|
|
78
78
|
"go:petstore2": "yarn build && yarn counterfact https://petstore.swagger.io/v2/swagger.json out",
|
|
@@ -84,9 +84,6 @@
|
|
|
84
84
|
"@changesets/cli": "2.30.0",
|
|
85
85
|
"@eslint/js": "10.0.1",
|
|
86
86
|
"@jest/globals": "^30.3.0",
|
|
87
|
-
"@stryker-mutator/core": "9.6.0",
|
|
88
|
-
"@stryker-mutator/jest-runner": "9.6.0",
|
|
89
|
-
"@stryker-mutator/typescript-checker": "9.6.0",
|
|
90
87
|
"@swc/core": "1.15.21",
|
|
91
88
|
"@swc/jest": "0.2.39",
|
|
92
89
|
"@testing-library/dom": "10.4.1",
|
|
@@ -122,7 +119,6 @@
|
|
|
122
119
|
"jest-retries": "1.0.1",
|
|
123
120
|
"node-mocks-http": "1.17.2",
|
|
124
121
|
"rimraf": "6.1.3",
|
|
125
|
-
"stryker-cli": "1.1.0",
|
|
126
122
|
"supertest": "7.2.2",
|
|
127
123
|
"tsd": "0.33.0",
|
|
128
124
|
"using-temporary-files": "2.2.1"
|
|
@@ -141,11 +137,11 @@
|
|
|
141
137
|
"js-yaml": "4.1.1",
|
|
142
138
|
"json-schema-faker": "0.6.0",
|
|
143
139
|
"jsonwebtoken": "9.0.3",
|
|
144
|
-
"koa": "3.
|
|
140
|
+
"koa": "3.2.0",
|
|
145
141
|
"koa-bodyparser": "4.4.1",
|
|
146
142
|
"koa-proxies": "0.12.4",
|
|
147
143
|
"koa2-swagger-ui": "5.12.0",
|
|
148
|
-
"lodash": "4.
|
|
144
|
+
"lodash": "4.18.1",
|
|
149
145
|
"node-fetch": "3.3.2",
|
|
150
146
|
"open": "11.0.0",
|
|
151
147
|
"patch-package": "8.0.1",
|
|
File without changes
|
|
File without changes
|