Skip to content

Commit 5c85672

Browse files
committed
Merge branch 'master' into make-v23
2 parents 6e461dc + e51d3cd commit 5c85672

17 files changed

+133
-89
lines changed

.github/workflows/validations.yml

+7-36
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ jobs:
2323
- run: yarn test:oas
2424
- name: Build distribution
2525
run: yarn workspace express-zod-api build
26+
- name: Build tests
27+
run: yarn postbuild # builds the tests
2628
- name: Pack artifact
27-
run: yarn workspace express-zod-api pack --filename dist.tgz
29+
run: yarn workspace express-zod-api pack --filename ../compat-test/dist.tgz
2830
- name: Upload artifact
2931
uses: actions/upload-artifact@v4
3032
with:
3133
name: dist
32-
path: express-zod-api/dist.tgz
34+
path: compat-test
3335
Compatibility:
3436
name: express@${{matrix.express-version}}
3537
needs: OpenAPI
@@ -45,43 +47,12 @@ jobs:
4547
uses: actions/download-artifact@v4
4648
with:
4749
name: dist
48-
- name: Init package.json
49-
run: |
50-
cat >package.json <<EOF
51-
{ "type": "module", "private": true }
52-
EOF
5350
- name: Add dependencies
5451
run: |
5552
yarn add express@${{matrix.express-version}} [email protected] http-errors zod
56-
53+
yarn add -D [email protected] [email protected] vitest tsx
5754
yarn add express-zod-api@./dist.tgz
58-
- name: sample.ts
59-
run: |
60-
cat >sample.ts <<EOF
61-
const test: CustomHeaderSecurity = {};
62-
EOF
63-
- name: migration.spec.ts
64-
run: |
65-
cat >migration.spec.ts <<EOF
66-
import { readFile } from "node:fs/promises";
67-
describe("Migration", () => {
68-
test("should fix the import", async () => {
69-
const fixed = await readFile("./sample.ts", "utf-8");
70-
expect(fixed).toBe("const test: HeaderSecurity = {};\n");
71-
});
72-
});
73-
EOF
74-
- name: eslint.config.js
75-
run: |
76-
cat >eslint.config.js <<EOF
77-
import parser from "@typescript-eslint/parser";
78-
import migration from "express-zod-api/migration";
79-
export default [
80-
{ languageOptions: { parser }, plugins: { migration } },
81-
{ files: ["**/*.ts"], rules: { "migration/v23": "error" } },
82-
];
83-
EOF
84-
- name: Run migration test
55+
- name: Run tests
8556
run: |
8657
yarn eslint --fix
87-
yarn vitest --globals
58+
yarn vitest

compat-test/eslint.config.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import parser from "@typescript-eslint/parser";
2+
import migration from "express-zod-api/migration";
3+
4+
export default [
5+
{ languageOptions: { parser }, plugins: { migration } },
6+
{ files: ["**/*.ts"], rules: { "migration/v23": "error" } },
7+
];

compat-test/migration.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { readFile } from "node:fs/promises";
2+
import { describe, test, expect } from "vitest";
3+
4+
describe("Migration", () => {
5+
test("should fix the import", async () => {
6+
const fixed = await readFile("./sample.ts", "utf-8");
7+
expect(fixed).toBe(
8+
"const test: HeaderSecurity = {}; // eslint-disable-line @typescript-eslint/no-unused-vars\n",
9+
);
10+
});
11+
});

compat-test/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "module",
3+
"private": true,
4+
"description": "This file is a subject for populating by a CI workflow"
5+
}

compat-test/quick-start.spec.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { spawn } from "node:child_process";
2+
import {
3+
describe,
4+
vi,
5+
assert,
6+
afterAll,
7+
afterEach,
8+
expect,
9+
test,
10+
} from "vitest";
11+
12+
describe("ESM Test", async () => {
13+
let out = "";
14+
const listener = (chunk: Buffer) => {
15+
out += chunk.toString();
16+
};
17+
const quickStart = spawn("tsx", ["quick-start.ts"]);
18+
quickStart.stdout.on("data", listener);
19+
quickStart.stderr.on("data", listener);
20+
await vi.waitFor(() => assert(out.includes(`Listening`)), { timeout: 1e4 });
21+
22+
afterAll(async () => {
23+
quickStart.stdout.removeListener("data", listener);
24+
quickStart.stderr.removeListener("data", listener);
25+
quickStart.kill();
26+
await vi.waitFor(() => assert(quickStart.killed), { timeout: 1e4 });
27+
});
28+
29+
afterEach(() => {
30+
console.log(out);
31+
out = "";
32+
});
33+
34+
describe("Quick Start from Readme", () => {
35+
test("Should handle valid GET request", async () => {
36+
const response = await fetch(`http://localhost:8090/v1/hello?name=Rick`);
37+
expect(response.status).toBe(200);
38+
const json = await response.json();
39+
expect(json).toEqual({
40+
status: "success",
41+
data: {
42+
greetings: "Hello, Rick. Happy coding!",
43+
},
44+
});
45+
});
46+
});
47+
});

compat-test/sample.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const test: CustomHeaderSecurity = {}; // eslint-disable-line @typescript-eslint/no-unused-vars

eslint.config.js

+27-31
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
import globals from "globals"; // eslint-disable-line allowed/dependencies -- comes with eslint
2-
import jsPlugin from "@eslint/js"; // eslint-disable-line allowed/dependencies -- comes with eslint
1+
import globals from "globals";
2+
import jsPlugin from "@eslint/js";
33
import tsPlugin from "typescript-eslint";
44
import prettierOverrides from "eslint-config-prettier";
55
import prettierRules from "eslint-plugin-prettier/recommended";
66
import allowedDepsPlugin from "eslint-plugin-allowed-dependencies";
77
import { fileURLToPath } from "node:url";
88
import { dirname, join } from "node:path";
9+
import { builtinModules } from "node:module";
910

10-
const root = dirname(fileURLToPath(import.meta.url));
11-
const releaseDir = join(root, "express-zod-api");
12-
const exampleDir = join(root, "example");
11+
const releaseDir = join(
12+
dirname(fileURLToPath(import.meta.url)),
13+
"express-zod-api",
14+
);
15+
16+
const importConcerns = [
17+
{
18+
selector:
19+
"ImportDeclaration[source.value='ramda'] > ImportSpecifier, " +
20+
"ImportDeclaration[source.value='ramda'] > ImportDefaultSpecifier",
21+
message: "use import * as R from 'ramda'",
22+
},
23+
...builtinModules.map((mod) => ({
24+
selector: `ImportDeclaration[source.value='${mod}']`,
25+
message: `use node:${mod} for the built-in module`,
26+
})),
27+
];
1328

1429
const performanceConcerns = [
1530
{
@@ -20,12 +35,6 @@ const performanceConcerns = [
2035
selector: "MemberExpression[object.name='process'][property.name='env']", // #2144
2136
message: "Reading process.env is slow and must be memoized",
2237
},
23-
{
24-
selector:
25-
"ImportDeclaration[source.value='ramda'] > ImportSpecifier, " +
26-
"ImportDeclaration[source.value='ramda'] > ImportDefaultSpecifier",
27-
message: "use import * as R from 'ramda'",
28-
},
2938
{
3039
selector: "MemberExpression[object.name='R'] > Identifier[name='toPairs']", // #2168
3140
message: "R.toPairs() is 1.1x slower than Object.entries()",
@@ -173,20 +182,19 @@ export default tsPlugin.config(
173182
rules: {
174183
curly: ["warn", "multi-or-nest", "consistent"],
175184
"@typescript-eslint/no-shadow": "warn",
176-
"allowed/dependencies": [
177-
"error",
178-
{ development: true, ignore: ["express-zod-api"], packageDir: root },
179-
{ development: true, packageDir: releaseDir },
180-
{ packageDir: exampleDir },
181-
],
185+
"no-restricted-syntax": ["warn", ...importConcerns],
182186
},
183187
},
184188
{
185189
name: "source/all",
186190
files: ["express-zod-api/src/*.ts"],
187191
rules: {
188192
"allowed/dependencies": ["error", { packageDir: releaseDir }],
189-
"no-restricted-syntax": ["warn", ...performanceConcerns],
193+
"no-restricted-syntax": [
194+
"warn",
195+
...importConcerns,
196+
...performanceConcerns,
197+
],
190198
},
191199
},
192200
{
@@ -206,24 +214,12 @@ export default tsPlugin.config(
206214
rules: {
207215
"no-restricted-syntax": [
208216
"warn",
217+
...importConcerns,
209218
...performanceConcerns,
210219
...tsFactoryConcerns,
211220
],
212221
},
213222
},
214-
{
215-
name: "source/migration",
216-
files: [
217-
"express-zod-api/src/migration.ts",
218-
"express-zod-api/tests/migration.spec.ts",
219-
],
220-
rules: {
221-
"allowed/dependencies": [
222-
"error",
223-
{ ignore: ["^@typescript-eslint", "^\\."], packageDir: releaseDir },
224-
],
225-
},
226-
},
227223
{
228224
name: "tests/all",
229225
files: ["express-zod-api/tests/*.ts", "express-zod-api/vitest.setup.ts"],

example/assets/docs.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../example.documentation.yaml

example/config.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { BuiltinLogger, createConfig } from "express-zod-api";
22
import ui from "swagger-ui-express";
3-
import yaml from "yaml";
4-
import { readFile } from "node:fs/promises";
53
import createHttpError from "http-errors";
64

75
export const config = createConfig({
@@ -11,12 +9,13 @@ export const config = createConfig({
119
limitError: createHttpError(413, "The file is too large"), // affects uploadAvatarEndpoint
1210
},
1311
compression: true, // affects sendAvatarEndpoint
14-
beforeRouting: async ({ app }) => {
15-
// third-party middlewares serving their own routes or establishing their own routing besides the API
16-
const documentation = yaml.parse(
17-
await readFile("example.documentation.yaml", "utf-8"),
12+
// third-party middlewares serving their own routes or establishing their own routing besides the API
13+
beforeRouting: ({ app }) => {
14+
app.use(
15+
"/docs",
16+
ui.serve,
17+
ui.setup(null, { swaggerUrl: "/public/docs.yaml" }),
1818
);
19-
app.use("/docs", ui.serve, ui.setup(documentation));
2019
},
2120
inputSources: {
2221
patch: ["headers", "body", "params"], // affects authMiddleware used by updateUserEndpoint

example/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
"validate": "vitest run --globals validate.spec.ts"
1313
},
1414
"dependencies": {
15-
"swagger-ui-express": "^5.0.0",
16-
"yaml": "^2.7.0"
15+
"swagger-ui-express": "^5.0.0"
1716
},
1817
"devDependencies": {
1918
"@types/swagger-ui-express": "^4.1.8"

express-zod-api/bench/experiment.bench.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { has } from "ramda";
1+
import * as R from "ramda";
22
import { bench } from "vitest";
33

44
describe("Experiment for key lookup", () => {
@@ -13,7 +13,11 @@ describe("Experiment for key lookup", () => {
1313
});
1414

1515
bench("R.has", () => {
16-
return void (has("a", subject) && has("b", subject) && has("c", subject));
16+
return void (
17+
R.has("a", subject) &&
18+
R.has("b", subject) &&
19+
R.has("c", subject)
20+
);
1721
});
1822

1923
bench("Object.keys + includes", () => {

express-zod-api/src/migration.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
AST_NODE_TYPES as NT,
44
type TSESLint,
55
type TSESTree,
6-
} from "@typescript-eslint/utils";
6+
} from "@typescript-eslint/utils"; // eslint-disable-line allowed/dependencies -- special case
77

88
interface Queries {
99
headerSecurity: TSESTree.Identifier;

express-zod-api/tests/documentation-helpers.spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ReferenceObject } from "openapi3-ts/oas31";
2-
import { prop } from "ramda";
2+
import * as R from "ramda";
33
import { z } from "zod";
44
import { ez } from "../src";
55
import {
@@ -899,7 +899,7 @@ describe("Documentation helpers", () => {
899899
depictSecurityRefs(
900900
[[{ type: "apiKey" }, { type: "oauth2" }, { type: "openIdConnect" }]],
901901
[],
902-
prop("type"),
902+
R.prop("type"),
903903
),
904904
).toMatchSnapshot();
905905
expect(
@@ -909,7 +909,7 @@ describe("Documentation helpers", () => {
909909
[{ type: "apiKey" }, { type: "openIdConnect" }],
910910
],
911911
[],
912-
prop("type"),
912+
R.prop("type"),
913913
),
914914
).toMatchSnapshot();
915915
expect(
@@ -920,7 +920,7 @@ describe("Documentation helpers", () => {
920920
[{ type: "openIdConnect" }],
921921
],
922922
[],
923-
prop("type"),
923+
R.prop("type"),
924924
),
925925
).toMatchSnapshot();
926926
});
@@ -934,7 +934,7 @@ describe("Documentation helpers", () => {
934934
[{ type: "openIdConnect" }],
935935
],
936936
["read", "write"],
937-
prop("type"),
937+
R.prop("type"),
938938
),
939939
).toMatchSnapshot();
940940
});

tools/headers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { execSync } from "node:child_process";
22
import { writeFile } from "node:fs/promises";
3-
import { tryCatch } from "ramda";
3+
import * as R from "ramda";
44
import { z } from "zod";
55

66
/**
@@ -199,7 +199,7 @@ const responseOnlyHeaders = {
199199
};
200200

201201
const dest = "express-zod-api/src/well-known-headers.json";
202-
const mtime = tryCatch(
202+
const mtime = R.tryCatch(
203203
(cmd) => new Date(execSync(cmd, { encoding: "utf8" })),
204204
() => undefined,
205205
)(`git log -1 --pretty="format:%ci" ${dest}`);

tools/make-tests.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const quickStart = await extractQuickStartFromReadme();
1818
const testContent = {
1919
cjs: quickStart,
2020
esm: quickStart,
21+
compat: quickStart,
2122
issue952: quickStart.replace(/const/g, "export const"),
2223
};
2324

tools/ports.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { equals, nAry, when } from "ramda";
1+
import * as R from "ramda";
22

33
const disposer = (function* () {
44
let port = 8e3 + 1e2 * Number(process.env.VITEST_POOL_ID);
55
while (true) yield port++;
66
})();
77

88
export const givePort = (test?: "example", rsvd = 8090): number =>
9-
test ? rsvd : when(equals(rsvd), nAry(0, givePort))(disposer.next().value);
9+
test
10+
? rsvd
11+
: R.when(R.equals(rsvd), R.nAry(0, givePort))(disposer.next().value);

0 commit comments

Comments
 (0)