Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
891 changes: 338 additions & 553 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
"clean": "del-cli dist types",
"prebuild": "npm run clean",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build:code": "babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:only": "jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
Expand All @@ -60,8 +60,8 @@
"@babel/preset-env": "^7.16.7",
"@eslint/js": "^9.28.0",
"@eslint/markdown": "^7.1.0",
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@commitlint/cli": "^20.2.0",
"@commitlint/config-conventional": "^20.2.0",
"@fastify/express": "^4.0.2",
"@hapi/hapi": "^21.3.7",
"@hono/node-server": "^1.12.0",
Expand All @@ -73,7 +73,6 @@
"@types/on-finished": "^2.3.4",
"babel-jest": "^30.1.2",
"connect": "^3.7.0",
"cross-env": "^7.0.3",
"cspell": "^8.3.2",
"deepmerge": "^4.2.2",
"del-cli": "^6.0.0",
Expand All @@ -83,10 +82,10 @@
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-jsdoc": "^56.1.2",
"eslint-plugin-jsdoc": "^61.5.0",
"eslint-plugin-n": "^17.19.0",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-unicorn": "^61.0.2",
"eslint-plugin-unicorn": "^62.0.0",
"execa": "^5.1.1",
"express-4": "npm:express@^4",
"express": "^5.1.0",
Expand Down
25 changes: 12 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ const noop = () => {};
/** @typedef {import("http").IncomingMessage} IncomingMessage */
/** @typedef {import("http").ServerResponse & ExtendedServerResponse} ServerResponse */

// eslint-disable-next-line jsdoc/no-restricted-syntax
// eslint-disable-next-line jsdoc/reject-any-type
/** @typedef {any} EXPECTED_ANY */
// eslint-disable-next-line jsdoc/reject-function-type
/** @typedef {Function} EXPECTED_FUNCTION */

/**
* @callback NextFunction
* @param {any=} err error
* @param {EXPECTED_ANY=} err error
* @returns {void}
*/

Expand Down Expand Up @@ -386,21 +390,19 @@ function hapiWrapper() {

wdm.hapiWrapper = hapiWrapper;

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} kow wrapper
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} kow wrapper
*/
function koaWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {{req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | {message: string}, state: object}} ctx context
* @param {Function} next next
* @param {EXPECTED_FUNCTION} next next
* @returns {Promise<void>}
*/
async function webpackDevMiddleware(ctx, next) {
Expand Down Expand Up @@ -501,21 +503,19 @@ function koaWrapper(compiler, options) {

wdm.koaWrapper = koaWrapper;

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} hono wrapper
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} hono wrapper
*/
function honoWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {{ env: any, body: any, json: any, status: any, set: any, req: RequestInternal & import("./utils/compatibleAPI").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: any, status: any } }} context context
* @param {Function} next next function
* @param {{ env: EXPECTED_ANY, body: EXPECTED_ANY, json: EXPECTED_ANY, status: EXPECTED_ANY, set: EXPECTED_ANY, req: RequestInternal & import("./utils/compatibleAPI").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY } }} context context
* @param {EXPECTED_FUNCTION} next next function
* @returns {Promise<void>}
*/
async function webpackDevMiddleware(context, next) {
Expand Down Expand Up @@ -559,11 +559,10 @@ function honoWrapper(compiler, options) {
*/
res.getHeader = (name) => context.res.headers.get(name);

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {string} name header name
* @param {string | number | Readonly<string[]>} value value
* @returns {ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: any, status: any }} response
* @returns {ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY }} response
*/
res.setHeader = (name, value) => {
context.res.headers.append(name, value);
Expand Down
18 changes: 8 additions & 10 deletions src/utils/compatibleAPI.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("../index").IncomingMessage} IncomingMessage */
/** @typedef {import("../index").ServerResponse} ServerResponse */
/** @typedef {import("../index").OutputFileSystem} OutputFileSystem */
/** @typedef {import("../index").EXPECTED_ANY} EXPECTED_ANY */

/**
* @typedef {object} ExpectedIncomingMessage
Expand All @@ -9,7 +10,6 @@
* @property {(() => string | undefined)=} getURL get URL extra method
*/

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @typedef {object} ExpectedServerResponse
* @property {((status: number) => void)=} setStatusCode set status code
Expand All @@ -21,9 +21,9 @@
* @property {((data?: string | Buffer) => void)=} finish finish
* @property {(() => string[])=} getResponseHeaders get response header
* @property {(() => boolean)=} getHeadersSent get headers sent
* @property {((data: any) => void)=} stream stream
* @property {(() => any)=} getOutgoing get outgoing
* @property {((name: string, value: any) => void)=} setState set state
* @property {((data: EXPECTED_ANY) => void)=} stream stream
* @property {(() => EXPECTED_ANY)=} getOutgoing get outgoing
* @property {((name: string, value: EXPECTED_ANY) => void)=} setState set state
*/

/**
Expand Down Expand Up @@ -280,12 +280,11 @@ function initState(res) {
res.locals ||= {};
}

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @param {any} value state
* @param {EXPECTED_ANY} value state
* @returns {void}
*/
function setState(res, name, value) {
Expand All @@ -295,8 +294,7 @@ function setState(res, name, value) {
return;
}

// eslint-disable-next-line jsdoc/no-restricted-syntax
/** @type {any} */
/** @type {Record<string, EXPECTED_ANY>} */
(res.locals)[name] = value;
}

Expand Down
10 changes: 5 additions & 5 deletions src/utils/memorize.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** @typedef {import("../index").EXPECTED_ANY} EXPECTED_ANY */

const cacheStore = new WeakMap();

// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template T
* @typedef {(...args: any) => T} FunctionReturning
* @typedef {(...args: EXPECTED_ANY) => T} FunctionReturning
*/

/**
Expand All @@ -14,10 +15,9 @@ const cacheStore = new WeakMap();
* @returns {FunctionReturning<T>} new function
*/
function memorize(fn, { cache = new Map() } = {}, callback = undefined) {
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {any} arguments_ args
* @returns {any} result
* @param {EXPECTED_ANY[]} arguments_ args
* @returns {EXPECTED_ANY} result
*/
const memoized = (...arguments_) => {
const [key] = arguments_;
Expand Down
136 changes: 71 additions & 65 deletions test/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3161,6 +3161,7 @@ describe.each([

describe("should handle custom fs errors and response 500 code without `fs.createReadStream`", () => {
let compiler;
// Try to disable this

const outputPath = path.resolve(
__dirname,
Expand Down Expand Up @@ -4004,83 +4005,87 @@ describe.each([
});

describe("writeToDisk option", () => {
describe('should work with "true" value', () => {
let compiler;
(name === "hono" ? describe.only : describe)(
'should work with "true" value',
() => {
let compiler;

const outputPath = path.resolve(
__dirname,
"./outputs/write-to-disk-true",
);
const outputPath = path.resolve(
__dirname,
"./outputs/write-to-disk-true",
);

beforeAll(async () => {
compiler = getCompiler({
...webpackConfig,
output: {
filename: "bundle.js",
path: outputPath,
publicPath: "/public/",
},
});
beforeAll(async () => {
compiler = getCompiler({
...webpackConfig,
output: {
filename: "bundle.js",
path: outputPath,
publicPath: "/public/",
},
});

[server, req, instance] = await frameworkFactory(
name,
framework,
compiler,
{ writeToDisk: true },
);
});
[server, req, instance] = await frameworkFactory(
name,
framework,
compiler,
{ writeToDisk: true },
);
});

afterAll(async () => {
await fs.promises.rm(outputPath, {
recursive: true,
force: true,
afterAll(async () => {
await fs.promises.rm(outputPath, {
recursive: true,
force: true,
});
await close(server, instance);
});
await close(server, instance);
});

it("should find the bundle file on disk", (done) => {
req.get("/public/bundle.js").expect(200, (error) => {
if (error) {
return done(error);
}
it("should find the bundle file on disk", (done) => {
req.get("/public/bundle.js").expect(200, (error) => {
if (error) {
return done(error);
}

const bundlePath = path.resolve(
__dirname,
"./outputs/write-to-disk-true/bundle.js",
);
const bundlePath = path.resolve(
__dirname,
"./outputs/write-to-disk-true/bundle.js",
);

expect(
compiler.hooks.assetEmitted.taps.filter(
(hook) => hook.name === "DevMiddleware",
),
).toHaveLength(1);
expect(fs.existsSync(bundlePath)).toBe(true);
expect(
compiler.hooks.assetEmitted.taps.filter(
(hook) => hook.name === "DevMiddleware",
),
).toHaveLength(1);
expect(fs.existsSync(bundlePath)).toBe(true);

instance.invalidate();
instance.invalidate();

return compiler.hooks.done.tap(
"DevMiddlewareWriteToDiskTest",
() => {
expect(
compiler.hooks.assetEmitted.taps.filter(
(hook) => hook.name === "DevMiddleware",
),
).toHaveLength(1);
return compiler.hooks.done.tap(
"DevMiddlewareWriteToDiskTest",
() => {
expect(
compiler.hooks.assetEmitted.taps.filter(
(hook) => hook.name === "DevMiddleware",
),
).toHaveLength(1);

done();
},
);
done();
},
);
});
});
});

it("should not allow to get files above root", async () => {
const response = await req.get("/public/..%2f../middleware.test.js");
it("should not allow to get files above root", async () => {
const response = await req.get(
"/public/..%2f../middleware.test.js",
);

expect(response.statusCode).toBe(403);
expect(response.headers["content-type"]).toBe(
"text/html; charset=utf-8",
);
expect(response.text).toBe(`<!DOCTYPE html>
expect(response.statusCode).toBe(403);
expect(response.headers["content-type"]).toBe(
"text/html; charset=utf-8",
);
expect(response.text).toBe(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
Expand All @@ -4090,8 +4095,9 @@ describe.each([
<pre>Forbidden</pre>
</body>
</html>`);
});
});
});
},
);

describe('should work with "true" value when the `output.clean` is `true`', () => {
const outputPath = path.resolve(
Expand Down
Loading