├── README.md
├── example
├── typescript
│ ├── .gitignore
│ ├── README.md
│ ├── mocker
│ │ ├── user.mock.js
│ │ └── index.js
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── package.json
├── base
│ ├── .gitignore
│ ├── README.md
│ ├── mocker
│ │ ├── user.mock.js
│ │ └── index.js
│ └── package.json
├── express
│ ├── .gitignore
│ ├── README.md
│ ├── index.js
│ ├── mocker
│ │ ├── user.mock.js
│ │ └── index.js
│ └── package.json
├── loadData
│ ├── .gitignore
│ ├── README.md
│ ├── mocker
│ │ ├── db
│ │ │ ├── user.js
│ │ │ └── userInfo.json
│ │ └── index.js
│ └── package.json
├── webpack
│ ├── .gitignore
│ ├── README.md
│ ├── mocker
│ │ ├── user.js
│ │ └── index.js
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── webpack.config.js
│ └── src
│ │ └── index.js
└── create-react-app
│ ├── README.md
│ ├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
│ ├── mocker
│ ├── db
│ │ ├── user.js
│ │ └── userInfo.js
│ └── index.js
│ ├── src
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.js
│ ├── setupProxy.js
│ ├── index.css
│ ├── App.css
│ ├── App.js
│ └── logo.svg
│ ├── .gitignore
│ ├── package.json
│ └── .eslintcache
├── lerna.json
├── packages
└── core
│ ├── src
│ ├── __test__
│ │ └── index.test.ts
│ ├── utils.ts
│ ├── delay.ts
│ ├── proxyHandle.ts
│ ├── mockerHandle.ts
│ ├── bin
│ │ └── mocker.ts
│ └── index.ts
│ ├── tsconfig.json
│ ├── package.json
│ └── README.md
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ └── FEATURE_REQUEST.md
└── workflows
│ └── ci.yml
├── renovate.json
├── .gitignore
├── LICENSE
├── package.json
└── README-zh.md
/README.md:
--------------------------------------------------------------------------------
1 | packages/core/README.md
--------------------------------------------------------------------------------
/example/typescript/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
--------------------------------------------------------------------------------
/example/base/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | package-lock.json
--------------------------------------------------------------------------------
/example/express/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | package-lock.json
--------------------------------------------------------------------------------
/example/loadData/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | package-lock.json
--------------------------------------------------------------------------------
/example/webpack/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | package-lock.json
--------------------------------------------------------------------------------
/example/create-react-app/README.md:
--------------------------------------------------------------------------------
1 | Mocker API Example
2 | ---
3 |
4 | ```bash
5 | npm run start
6 | ```
7 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "3.0.2",
3 | "packages": [
4 | "packages/*",
5 | "example/*"
6 | ]
7 | }
--------------------------------------------------------------------------------
/example/create-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/example/create-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/example/create-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/logo192.png
--------------------------------------------------------------------------------
/example/create-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/logo512.png
--------------------------------------------------------------------------------
/packages/core/src/__test__/index.test.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | describe('sum', () => {
4 | it('works', () => {
5 | expect(2).toEqual(2);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/example/typescript/README.md:
--------------------------------------------------------------------------------
1 | typescript
2 | ===
3 |
4 | ```bash
5 | npm run watch
6 | npm run build
7 | ```
8 |
9 | ```bash
10 | npm run start
11 | ```
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: jaywcjlove
2 | buy_me_a_coffee: jaywcjlove
3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"]
4 |
--------------------------------------------------------------------------------
/example/base/README.md:
--------------------------------------------------------------------------------
1 | Mocker API Example
2 | ---
3 |
4 | ```bash
5 | npm install
6 | npm run api
7 | ```
8 |
9 | ## test
10 |
11 | ```bash
12 | curl http://localhost:3721/repos/hello
13 | ```
--------------------------------------------------------------------------------
/example/express/README.md:
--------------------------------------------------------------------------------
1 | Mocker API Example
2 | ---
3 |
4 |
5 | ```bash
6 | npm install
7 | npm run start
8 | ```
9 |
10 | ## test
11 |
12 | ```bash
13 | curl http://localhost:8080/api/jobs
14 | ```
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "packageRules": [
6 | {
7 | "matchPackagePatterns": ["*"],
8 | "rangeStrategy": "replace"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/example/loadData/README.md:
--------------------------------------------------------------------------------
1 | Mocker API Example
2 | ---
3 |
4 | ```bash
5 | npm install
6 | npm run api
7 | ```
8 |
9 | ## test
10 |
11 | ```bash
12 | curl http://localhost:3721/api/user
13 | curl http://localhost:3721/api/user/info
14 | ```
--------------------------------------------------------------------------------
/example/webpack/README.md:
--------------------------------------------------------------------------------
1 | Mocker API Example
2 | ---
3 |
4 | ```bash
5 | npm install
6 | npm run start
7 | open http://localhost:8082
8 | ```
9 |
10 | ## test
11 |
12 | ```bash
13 | curl http://localhost:8080/api/userinfo/2342a-234ab-342
14 | ```
--------------------------------------------------------------------------------
/example/express/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const apiMocker = require('mocker-api');
3 |
4 | const app = express();
5 |
6 | apiMocker(app, require.resolve('./mocker/index'))
7 | app.listen(8080);
8 | console.log('=> http://localhost:8080')
--------------------------------------------------------------------------------
/example/loadData/mocker/db/user.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (req, res) => {
3 | return res.json([
4 | {
5 | id: 1,
6 | username: 'kenny',
7 | sex: 34
8 | }, {
9 | id: 2,
10 | username: 'mocker',
11 | sex: 23
12 | }
13 | ]);
14 | }
--------------------------------------------------------------------------------
/example/create-react-app/mocker/db/user.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (req, res) => {
3 | return res.json([
4 | {
5 | id: 1,
6 | username: 'kenny',
7 | sex: 34
8 | }, {
9 | id: 2,
10 | username: 'mocker',
11 | sex: 23
12 | }
13 | ]);
14 | }
--------------------------------------------------------------------------------
/example/create-react-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/example/create-react-app/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/example/create-react-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 |
--------------------------------------------------------------------------------
/example/base/mocker/user.mock.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = '/api/userinfo';
2 |
3 | const release = {
4 | [`GET ${BASE_URL}/:id`]: (req, res) => {
5 | console.log('---->', req.params)
6 | return res.json({
7 | id: 1,
8 | username: 'kenny',
9 | sex: 6
10 | });
11 | }
12 | }
13 |
14 | module.exports = release
--------------------------------------------------------------------------------
/example/express/mocker/user.mock.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = '/api/userinfo';
2 |
3 | const release = {
4 | [`GET ${BASE_URL}/:id`]: (req, res) => {
5 | console.log('---->', req.params)
6 | return res.json({
7 | id: 1,
8 | username: 'kenny',
9 | sex: 6
10 | });
11 | }
12 | }
13 |
14 | module.exports = release
--------------------------------------------------------------------------------
/example/create-react-app/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const apiMocker = require('mocker-api');
2 | const path = require('path');
3 |
4 | module.exports = function(app) {
5 | apiMocker(app, path.resolve('./mocker/index.js'), {
6 | proxy: {
7 | '/repos/*path': 'https://api.github.com/',
8 | },
9 | changeHost: true,
10 | });
11 | };
--------------------------------------------------------------------------------
/example/typescript/mocker/user.mock.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = '/api/userinfo';
2 |
3 | const release = {
4 | [`GET ${BASE_URL}/:id`]: (req, res) => {
5 | console.log('---->', req.params)
6 | return res.json({
7 | id: 1,
8 | username: 'kenny',
9 | sex: 6
10 | });
11 | }
12 | }
13 |
14 | module.exports = release
--------------------------------------------------------------------------------
/example/loadData/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/load-data",
3 | "version": "3.0.2",
4 | "description": "",
5 | "private": true,
6 | "scripts": {
7 | "api": "mocker ./mocker"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "devDependencies": {
12 | "mocker-api": "^3.0.2"
13 | },
14 | "license": "MIT"
15 | }
16 |
--------------------------------------------------------------------------------
/example/base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/base",
3 | "version": "3.0.2",
4 | "description": "",
5 | "private": true,
6 | "scripts": {
7 | "api": "mocker ./mocker"
8 | },
9 | "keywords": [],
10 | "mocker": {
11 | "port": 7788
12 | },
13 | "author": "",
14 | "devDependencies": {
15 | "mocker-api": "^3.0.2"
16 | },
17 | "license": "MIT"
18 | }
19 |
--------------------------------------------------------------------------------
/example/create-react-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/example/create-react-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/example/typescript/src/index.ts:
--------------------------------------------------------------------------------
1 | import apiMocker from 'mocker-api';
2 | import express from 'express';
3 | import path from 'path';
4 |
5 | const app = express();
6 |
7 | // apiMocker(app, {
8 | // _proxy: {
9 | // changeHost: true,
10 | // },
11 | // 'GET /api/user': {
12 | // id: 1,
13 | // sex: 0
14 | // }
15 | // });
16 |
17 | apiMocker(app, path.resolve('./mocker/index.js'));
18 | app.listen(8080);
19 |
20 |
21 | console.log('=> http://localhost:8080')
--------------------------------------------------------------------------------
/example/webpack/mocker/user.js:
--------------------------------------------------------------------------------
1 | exports.login = function (req, res) {
2 | const { password, username } = req.body;
3 | if (password === '888888' && username === 'admin') {
4 | return res.json({
5 | status: 'ok',
6 | code: 0,
7 | token: "sdfsdfsdfdsf",
8 | data: {
9 | id: 1,
10 | username: 'kenny',
11 | sex: 6
12 | }
13 | });
14 | } else {
15 | return res.json({
16 | status: 'error',
17 | code: 403
18 | });
19 | }
20 | }
--------------------------------------------------------------------------------
/example/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/express",
3 | "version": "3.0.2",
4 | "description": "",
5 | "private": true,
6 | "scripts": {
7 | "start": "nodemon index.js"
8 | },
9 | "nodemonConfig": {
10 | "ignore": [
11 | "node_modules/*"
12 | ]
13 | },
14 | "author": "",
15 | "license": "MIT",
16 | "dependencies": {
17 | "express": "~4.21.0",
18 | "nodemon": "~2.0.20"
19 | },
20 | "devDependencies": {
21 | "mocker-api": "^3.0.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "declaration": true,
14 | "experimentalDecorators": true,
15 | "baseUrl": "src",
16 | },
17 | "include": ["src/**/*"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "resolveJsonModule": true,
5 | "esModuleInterop": true,
6 | "allowSyntheticDefaultImports": true,
7 | "declaration": true,
8 | "noImplicitAny": true,
9 | "strict": false,
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "sourceMap": true,
13 | "skipLibCheck": true,
14 | "outDir": "lib",
15 | "types": ["jest", "node"],
16 | "baseUrl": "."
17 | },
18 | "include": ["src/**/*"]
19 | }
20 |
--------------------------------------------------------------------------------
/example/loadData/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 |
3 | // 是否禁用代理
4 | const noProxy = process.env.NO_PROXY === 'true';
5 |
6 | function loadData(data) {
7 | const result = {};
8 | Object.keys(data).forEach((key) => {
9 | result[key] = require(data[key]);
10 | });
11 | return result;
12 | }
13 |
14 | const proxy = loadData({
15 | 'GET /api/user': './db/user',
16 | 'GET /api/user/info': './db/userInfo',
17 | 'OPTIONS /api/user/info': './db/userInfo',
18 | });
19 |
20 | module.exports = (noProxy ? {} : delay(proxy, 1000));
21 |
--------------------------------------------------------------------------------
/example/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/typescript",
3 | "version": "3.0.2",
4 | "private": true,
5 | "description": "",
6 | "main": "index.js",
7 | "scripts": {
8 | "start": "node lib/index.js",
9 | "watch": "tsc -p ./ --types --outDir lib --watch",
10 | "build": "tsc -p ./ --types --outDir lib",
11 | "test": "tsc --noEmit"
12 | },
13 | "author": "jaywcjlove",
14 | "license": "MIT",
15 | "dependencies": {
16 | "@types/express": "~4.17.14",
17 | "express": "~4.21.0",
18 | "mocker-api": "^3.0.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/webpack",
3 | "version": "3.0.2",
4 | "private": true,
5 | "description": "Mocker API Example",
6 | "scripts": {
7 | "start": "webpack serve --progress --mode development",
8 | "build": "webpack --mode production"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@webpack-cli/serve": "~2.0.1",
15 | "html-webpack-plugin": "~5.6.0",
16 | "mocker-api": "^3.0.2",
17 | "webpack": "^5.77.0",
18 | "webpack-cli": "^5.0.1",
19 | "webpack-dev-server": "^4.13.2"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/create-react-app/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 |
3 | // 是否禁用代理
4 | const noProxy = process.env.NO_PROXY === 'true';
5 |
6 | function loadData(data) {
7 | const result = {};
8 | Object.keys(data).forEach((key) => {
9 | result[key] = require(data[key]);
10 | });
11 | return result;
12 | }
13 |
14 | const proxy = loadData({
15 | 'GET /api/user': './db/user',
16 | 'GET /api/user/info': './db/userInfo',
17 | 'GET /api/userinfo/:id': './db/userInfo',
18 | 'OPTIONS /api/user/info': './db/userInfo',
19 | });
20 |
21 | module.exports = (noProxy ? {} : delay(proxy, 1000));
22 |
--------------------------------------------------------------------------------
/example/create-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | lib
4 | node_modules
5 | npm-debug.log*
6 | coverage
7 | typedoc
8 | docs
9 |
10 | # misc
11 | .DS_Store
12 | .env.local
13 | .env.development.local
14 | .env.test.local
15 | .env.production.local
16 |
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 | yarn.lock
21 | lerna-debug.log
22 | yarn-error.log
23 | package-lock.json
24 |
25 | .DS_Store
26 | .cache
27 | .vscode
28 | .idea
29 | .env
30 |
31 | *.mpassword
32 | *.bak
33 | *.tem
34 | *.temp
35 | #.swp
36 | *.*~
37 | ~*.*
38 |
39 | # IDEA
40 | *.iml
41 | *.ipr
42 | *.iws
43 | .idea/
--------------------------------------------------------------------------------
/example/webpack/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | webpack api mocker
8 |
9 |
10 | Visit :/mocker/index.js to customize the API Try it?
11 |
12 | id:
13 | name:
14 | age:
15 |
16 | URL: /repos/jaywcjlove/github-rank:
17 | URL: /repos/jaywcjlove/webpack-api-mocker:
18 |
19 |
--------------------------------------------------------------------------------
/example/create-react-app/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | name: Bug report
4 | about: File a report when something goes wrong so it can get fixed!
5 | title: 'Bug: '
6 |
7 | ---
8 |
9 |
10 |
11 | **How To Reproduce**
12 |
13 | Node: x.x.x
14 | Webpack: x.x.x
15 | Mocker Api: x.x.x
16 |
17 | 1. ...
18 | 2. ...
19 |
20 | **Expected behavior**
21 |
22 |
23 |
24 | **Actual behavior**
25 |
26 |
27 |
28 | **Screenshots**
29 |
30 |
31 |
32 | **Additional context**
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/loadData/mocker/db/userInfo.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 2,
3 | "username": "jack_smith",
4 | "email": "jack@example.com",
5 | "name": "Jack Smith",
6 | "state": "blocked",
7 | "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
8 | "web_url": "http://localhost:3000/jack_smith",
9 | "created_at": "2012-05-23T08:01:01Z",
10 | "is_admin": false,
11 | "bio": null,
12 | "location": null,
13 | "skype": "",
14 | "linkedin": "",
15 | "twitter": "",
16 | "website_url": "",
17 | "organization": "",
18 | "last_sign_in_at": null,
19 | "confirmed_at": "2012-05-30T16:53:06.148Z",
20 | "theme_id": 1,
21 | "last_activity_on": "2012-05-23",
22 | "color_scheme_id": 3,
23 | "projects_limit": 100,
24 | "current_sign_in_at": "2014-03-19T17:54:13Z",
25 | "identities": [],
26 | "can_create_group": true,
27 | "can_create_project": true,
28 | "two_factor_enabled": true,
29 | "external": false,
30 | "private_profile": false
31 | }
--------------------------------------------------------------------------------
/example/create-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/create-react-app",
3 | "version": "3.0.2",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "~5.16.5",
7 | "@testing-library/react": "~14.0.0",
8 | "@testing-library/user-event": "~14.4.3",
9 | "react": "~18.2.0",
10 | "react-dom": "~18.2.0",
11 | "react-scripts": "~5.0.1"
12 | },
13 | "devDependencies": {
14 | "mocker-api": "^3.0.2"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const apiMocker = require('mocker-api');
4 |
5 | module.exports = {
6 | entry: {
7 | app: './src/index.js',
8 | },
9 | output: {
10 | filename: '[name].bundle.js',
11 | path: path.resolve(__dirname, 'dist')
12 | },
13 | devServer: {
14 | port: 8082,
15 | onBeforeSetupMiddleware: (devServer) => {
16 | apiMocker(devServer.app, path.resolve('./mocker/index.js'), {
17 | proxy: {
18 | '/repos/(.*)': 'https://api.github.com/',
19 | },
20 | changeHost: true,
21 | })
22 |
23 | },
24 | // before(app){
25 | // apiMocker(app, path.resolve('./mocker/index.js'), {
26 | // proxy: {
27 | // '/repos/(.*)': 'https://api.github.com/',
28 | // },
29 | // changeHost: true,
30 | // })
31 | // }
32 | },
33 | plugins: [
34 | new HtmlWebpackPlugin({
35 | template: path.resolve('./public/index.html'),
36 | title: 'Webpack App Mocker API'
37 | })
38 | ],
39 | };
--------------------------------------------------------------------------------
/packages/core/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as toRegexp from 'path-to-regexp';
3 | import type { PathToRegexpOptions, ParseOptions, Keys } from 'path-to-regexp';
4 |
5 | const pathToRegexp = toRegexp.pathToRegexp;
6 |
7 | export function pathMatch(options: PathToRegexpOptions & ParseOptions) {
8 | options = options || {};
9 | return function (path: string) {
10 | var keys: Keys = [];
11 | let regexpObject: RegExp | undefined = undefined
12 | try {
13 | var re = pathToRegexp(path, options);
14 | regexpObject = re.regexp;
15 | keys = re.keys;
16 | } catch (error) {
17 | console.error(error);
18 | }
19 | return function (pathname: string, params?: any) {
20 | var mData = regexpObject?.exec(pathname);
21 | if (!mData) return false;
22 | params = params || {};
23 | var key, param;
24 | for (var i = 0; i < keys.length; i++) {
25 | key = keys[i];
26 | param = mData[i + 1];
27 | if (!param) continue;
28 | params[key.name] = decodeURIComponent(param);
29 | }
30 | return params;
31 | }
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 小弟调调™
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/create-react-app/mocker/db/userInfo.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (req, res) => {
3 | return res.json({
4 | "id": req.params.id ? req.params.id : 2,
5 | "username": "jack_smith",
6 | "email": "jack@example.com",
7 | "name": "Jack Smith",
8 | "state": "blocked",
9 | "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
10 | "web_url": "http://localhost:3000/jack_smith",
11 | "created_at": "2012-05-23T08:01:01Z",
12 | "is_admin": false,
13 | "bio": null,
14 | "sex": '2',
15 | "location": null,
16 | "skype": "",
17 | "linkedin": "",
18 | "twitter": "",
19 | "website_url": "",
20 | "organization": "",
21 | "last_sign_in_at": null,
22 | "confirmed_at": "2012-05-30T16:53:06.148Z",
23 | "theme_id": 1,
24 | "last_activity_on": "2012-05-23",
25 | "color_scheme_id": 3,
26 | "projects_limit": 100,
27 | "current_sign_in_at": "2014-03-19T17:54:13Z",
28 | "identities": [],
29 | "can_create_group": true,
30 | "can_create_project": true,
31 | "two_factor_enabled": true,
32 | "external": false,
33 | "private_profile": false
34 | });
35 | }
--------------------------------------------------------------------------------
/example/webpack/src/index.js:
--------------------------------------------------------------------------------
1 | fetch('/api/userinfo/1314-sd', {
2 | 'Accept': 'application/json',
3 | 'Content-Type': 'application/x-www-form-urlencoded',
4 | }).then((response) => response.json())
5 | .then(data => {
6 | console.log('data:', data);
7 | document.getElementById('name').innerHTML = data.username;
8 | document.getElementById('age').innerHTML = data.sex;
9 | document.getElementById('id').innerHTML = data.id;
10 | });
11 |
12 | fetch('/repos/jaywcjlove/github-rank',)
13 | .then(response => response.json())
14 | .then(data => {
15 | console.log('data:1', data)
16 | document.getElementById('mock').innerText = `from github api: webpack-api-mocker star count: ${data.stargazers_count}`;
17 | });
18 |
19 | fetch('/repos/jaywcjlove/webpack-api-mocker')
20 | .then(response => response.json())
21 | .then(data => {
22 | document.getElementById('github').innerText = `from github api: webpack-api-mocker star count: ${data.stargazers_count}`
23 | });
24 |
25 | fetch('/api/login/account', {
26 | method: 'POST',
27 | headers: {
28 | 'Content-Type': 'application/json'
29 | },
30 | body: JSON.stringify({ password: '888888', username: 'admin' })
31 | })
--------------------------------------------------------------------------------
/example/create-react-app/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/index.js":"1","/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/App.js":"2","/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/reportWebVitals.js":"3"},{"size":500,"mtime":1608791014252,"results":"4","hashOfConfig":"5"},{"size":1923,"mtime":1608792706897,"results":"6","hashOfConfig":"5"},{"size":362,"mtime":1608791014254,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"10"},"7ndr3s",{"filePath":"11","messages":"12","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"13","messages":"14","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"10"},"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/index.js",[],["15","16"],"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/App.js",[],"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/reportWebVitals.js",[],{"ruleId":"17","replacedBy":"18"},{"ruleId":"19","replacedBy":"20"},"no-native-reassign",["21"],"no-negated-in-lhs",["22"],"no-global-assign","no-unsafe-negation"]
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | name: Feature request
4 | about: Suggest new feature for this project
5 | title: 'Feature: '
6 |
7 | ---
8 |
9 |
10 |
11 | **Is your feature request related to a problem? Please describe.**
12 |
13 |
14 |
15 | **Describe the solution you'd like**
16 |
17 |
18 |
19 | **Describe alternatives you've considered**
20 |
21 |
22 |
23 | **Additional context**
24 |
25 |
26 |
27 | ### Issue Checklist
28 |
29 |
30 |
31 | - [ ] I have checked for other similar issues
32 | - [ ] I have explained why this change is important
33 | - [ ] I have added necessary documentation (if appropriate)
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mocker-api",
3 | "private": true,
4 | "description": "This is dev support mock RESTful API.",
5 | "workspaces": [
6 | "packages/*",
7 | "example/*"
8 | ],
9 | "scripts": {
10 | "version": "lerna version --no-changelog --no-git-tag-version --no-push",
11 | "doc": "lerna exec --scope mocker-api -- typedoc src/index.ts src/delay.ts --name mocker-api --out docs/type",
12 | "build": "lerna exec --scope mocker-api -- tsbb build",
13 | "watch": "lerna exec --scope mocker-api -- tsbb watch",
14 | "test": "lerna exec --scope mocker-api -- tsbb test",
15 | "coverage": "lerna exec --scope mocker-api -- tsbb test --coverage",
16 | "example:base": "lerna exec --scope mocker-api -- npm run api",
17 | "example:create-react-app": "lerna exec --scope @example/create-react-app -- npm run build",
18 | "example:typescript": "lerna exec --scope @example/typescript -- npm run build",
19 | "example:webpack": "lerna exec --scope @example/webpack -- npm run build",
20 | "clean": "lerna clean --yes"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/jaywcjlove/mocker-api.git"
25 | },
26 | "jest": {},
27 | "engines": {
28 | "node": ">=16.0.0"
29 | },
30 | "license": "MIT",
31 | "author": "Kenny Wong ",
32 | "markdown-to-html": {
33 | "reurls": {
34 | "README-zh.md": "index.zh.html",
35 | "README.md": "index.html"
36 | }
37 | },
38 | "devDependencies": {
39 | "lerna": "^8.0.0",
40 | "tsbb": "^4.1.3",
41 | "typedoc": "~0.23.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/src/delay.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from 'express';
2 | import { MockerProxyRoute, MockerResult, MockerResultFunction } from './';
3 |
4 | /**
5 | * You can use functional tool to enhance mock. [#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17)
6 | *
7 | * ```js
8 | * const delay = require('mocker-api/delay');
9 | * const noProxy = process.env.NO_PROXY === 'true';
10 | *
11 | * const proxy = {
12 | * 'GET /api/user': {
13 | * id: 1,
14 | * username: 'kenny',
15 | * sex: 6
16 | * },
17 | * // ...
18 | * }
19 | * module.exports = (noProxy ? {} : delay(proxy, 1000));
20 | * ```
21 | */
22 |
23 | module.exports = delay;
24 |
25 | export default function delay(proxy: MockerProxyRoute, timer: number = 0): MockerResult {
26 | const mockApi: MockerResult = {};
27 | Object.keys(proxy).forEach((key) => {
28 | const result = proxy[key];
29 | if ((Object.prototype.toString.call(result) === '[object String]' && /^http/.test(result as string)) || key === '_proxy' || timer === 0) {
30 | mockApi[key] = proxy[key];
31 | } else {
32 | mockApi[key] = function (req: Request, res: Response) {
33 | let foo: MockerResultFunction;
34 | if (Object.prototype.toString.call(result) === '[object Function]') {
35 | foo = result as MockerResultFunction;
36 | } else {
37 | foo = (_req: Request, _res: Response) => {
38 | return _res.json(result);
39 | };
40 | }
41 | setTimeout(() => {
42 | foo(req, res);
43 | }, timer);
44 | };
45 | }
46 | });
47 |
48 | return mockApi;
49 | }
50 |
--------------------------------------------------------------------------------
/packages/core/src/proxyHandle.ts:
--------------------------------------------------------------------------------
1 | import URL from 'url';
2 | import httpProxy from 'http-proxy';
3 | import { Request, Response } from 'express';
4 | import color from 'colors-cli/safe';
5 | import { MockerOption, HttpProxyListeners } from '.';
6 |
7 | export function proxyHandle(req: Request, res: Response, options: MockerOption = {}, proxyKey: string) {
8 | const currentProxy = options.proxy[proxyKey];
9 | const url = URL.parse(currentProxy);
10 | if (options.changeHost) {
11 | req.headers.host = url.host;
12 | }
13 | const { options: proxyOptions = {}, listeners: proxyListeners = {} as HttpProxyListeners }: MockerOption['httpProxy'] = options.httpProxy;
14 | /**
15 | * rewrite target's url path. Object-keys will be used as RegExp to match paths.
16 | * https://github.com/jaywcjlove/mocker-api/issues/62
17 | */
18 | Object.keys(options.pathRewrite).forEach(rgxStr => {
19 | const rePath = req.path.replace(new RegExp(rgxStr), options.pathRewrite[rgxStr]);
20 | const currentPath = [rePath];
21 | if (req.url.indexOf('?') > 0) {
22 | currentPath.push(req.url.replace(/(.*)\?/, ''));
23 | }
24 | req.query = URL.parse(req.url, true).query;
25 | req.url = req.originalUrl = currentPath.join('?');
26 | });
27 |
28 | const proxyHTTP = httpProxy.createProxyServer({});
29 | proxyHTTP.on('error', (err) => {
30 | console.error(`${color.red_b.black(` Proxy Failed: ${err.name}`)} ${err.message || ''} ${err.stack || ''} !!`);
31 | });
32 | Object.keys(proxyListeners).forEach(event => {
33 | proxyHTTP.on(event, proxyListeners[event]);
34 | });
35 |
36 | proxyHTTP.web(req, res, Object.assign({ target: url.href }, proxyOptions));
37 | }
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mocker-api",
3 | "version": "3.0.2",
4 | "description": "This is dev support mock RESTful API.",
5 | "homepage": "https://wangchujiang.com/mocker-api/",
6 | "funding": "https://jaywcjlove.github.io/#/sponsor",
7 | "bin": {
8 | "mocker": "lib/bin/mocker.js"
9 | },
10 | "main": "lib/index.js",
11 | "typings": "lib/index.d.ts",
12 | "exports": {
13 | ".": "./lib/index.js",
14 | "./delay": "./lib/delay.js",
15 | "./lib/delay": "./lib/delay.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/jaywcjlove/mocker-api.git"
20 | },
21 | "jest": {},
22 | "license": "MIT",
23 | "author": "Kenny Wong ",
24 | "files": [
25 | "src",
26 | "lib"
27 | ],
28 | "engines": {
29 | "node": ">=16.0.0"
30 | },
31 | "keywords": [
32 | "express",
33 | "webpack-dev-server",
34 | "webpack-api-mocker",
35 | "webpack",
36 | "mocker-api",
37 | "mock",
38 | "mocker",
39 | "api",
40 | "fetch",
41 | "react",
42 | "create-react-app",
43 | "TypeScript"
44 | ],
45 | "dependencies": {
46 | "body-parser": "~1.20.1",
47 | "chokidar": "~4.0.0",
48 | "clear-module": "~4.1.2",
49 | "colors-cli": "~1.0.28",
50 | "detect-port": "~1.6.0",
51 | "express": "~4.21.0",
52 | "http-proxy": "~1.18.1",
53 | "local-ip-url": "~1.0.3",
54 | "minimist": "~1.2.7",
55 | "path-to-regexp": "~8.2.0"
56 | },
57 | "devDependencies": {
58 | "@types/body-parser": "~1.19.2",
59 | "@types/detect-port": "~1.3.2",
60 | "@types/express": "~4.17.14",
61 | "@types/http-proxy": "~1.17.9",
62 | "@types/minimist": "~1.2.2",
63 | "@types/node": "~18.14.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/example/create-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/create-react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import './App.css';
3 | import { useEffect, useState } from 'react';
4 |
5 | function App() {
6 | const [data, setData] = useState({});
7 | const [githubRank, setGithubRank] = useState({});
8 | const [apiMocker, setApiMocker] = useState({});
9 | useEffect(() => {
10 | fetch('/api/userinfo/1314-sd', {
11 | 'Accept': 'application/json',
12 | 'Content-Type': 'application/x-www-form-urlencoded',
13 | }).then((response) => response.json())
14 | .then(data => {
15 | setData(data);
16 | console.log('data:', data);
17 | // document.getElementById('name').innerHTML = data.username;
18 | // document.getElementById('age').innerHTML = data.sex;
19 | // document.getElementById('id').innerHTML = data.id;
20 | });
21 | fetch('/repos/jaywcjlove/github-rank',)
22 | .then(response => response.json())
23 | .then(data => {
24 | setGithubRank(data);
25 | });
26 |
27 | fetch('/repos/jaywcjlove/webpack-api-mocker')
28 | .then(response => response.json())
29 | .then(data => {
30 | setApiMocker(data);
31 | });
32 | }, []);
33 | return (
34 |
35 |
36 |
37 |
38 | Edit src/App.js and save to reload.
39 |
40 |
46 | Learn React
47 |
48 |
49 |
id: {data.id}
50 |
name: {data.username}
51 |
age: {data.sex}
52 |
53 |
URL: /repos/jaywcjlove/github-rank: {githubRank.stargazers_count}
54 |
URL: /repos/jaywcjlove/webpack-api-mocker: {apiMocker.stargazers_count}
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default App;
62 |
--------------------------------------------------------------------------------
/example/express/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 | const user = require('./user.mock');
3 |
4 | // 是否禁用代理
5 | const noProxy = process.env.NO_PROXY === 'true';
6 |
7 | const proxy = {
8 | ...user,
9 | // 'GET /api/userinfo/:id': (req, res) => {
10 | // console.log('---->', req.params)
11 | // return res.json({
12 | // id: 1,
13 | // username: 'kenny',
14 | // sex: 6
15 | // });
16 | // },
17 | 'GET /api/user/list/:id/:type': (req, res) => {
18 | const { type } = req.params;
19 | if (type === 'webpack') {
20 | return res.status(403).json({
21 | status: 'error',
22 | code: 403
23 | });
24 | }
25 | return res.json([
26 | {
27 | id: 1,
28 | username: 'kenny',
29 | sex: 6
30 | }, {
31 | id: 2,
32 | username: 'kenny',
33 | sex: 6
34 | }
35 | ]);
36 | },
37 | 'GET /repos/hello': (req, res) => {
38 | return res.json({
39 | text: 'this is from mock server'
40 | });
41 | },
42 |
43 | 'GET /api/jobs/:id': (req, res) => {
44 | return res.json({
45 | text: 'url: /api/jobs/:id'
46 | });
47 | },
48 |
49 | 'GET /api/jobs': (req, res) => {
50 | return res.json({
51 | text: 'url: /api/jobs'
52 | });
53 | },
54 | 'POST /api/login/account': (req, res) => {
55 | const { password, username } = req.body;
56 | if (password === '888888' && username === 'admin') {
57 | return res.json({
58 | status: 'ok',
59 | code: 0,
60 | token: "sdfsdfsdfdsf",
61 | data: {
62 | id: 1,
63 | username: 'kenny',
64 | sex: 6
65 | }
66 | });
67 | } else {
68 | return res.json({
69 | status: 'error',
70 | code: 403
71 | });
72 | }
73 | },
74 | 'DELETE /api/user/:id': (req, res) => {
75 | console.log('---->', req.body)
76 | console.log('---->', req.params.id)
77 | res.send({ status: 'ok', message: '删除成功!' });
78 | },
79 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => {
80 | const { owner, repo, ref, path } = req.params;
81 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md
82 | // owner => admin
83 | // repo => webpack-mock-api
84 | // ref => master
85 | // req.params.path => add/ddd.md
86 | return res.json({
87 | id: 1,
88 | owner, repo, ref,
89 | path: path
90 | });
91 | },
92 | }
93 | module.exports = (noProxy ? {} : delay(proxy, 1000));
94 | // module.exports = proxy;
--------------------------------------------------------------------------------
/packages/core/src/mockerHandle.ts:
--------------------------------------------------------------------------------
1 | import URL from 'url';
2 | import bodyParser from 'body-parser';
3 | import { Request, Response, NextFunction } from 'express';
4 | import { MockerOption, MockerProxyRoute } from '.';
5 | import { pathMatch } from './utils';
6 |
7 | type MockerHandleOptions = {
8 | req: Request;
9 | res: Response;
10 | next: NextFunction;
11 | options: MockerOption;
12 | mocker: MockerProxyRoute;
13 | mockerKey: string;
14 | }
15 |
16 | export function mockerHandle(param: MockerHandleOptions) {
17 | const { options = {}, req, res, next, mocker, mockerKey } = param || {};
18 | let bodyParserMethd = bodyParser.json({ ...options.bodyParserJSON }); // 默认使用json解析
19 | /**
20 | * `application/x-www-form-urlencoded; charset=UTF-8` => `application/x-www-form-urlencoded`
21 | * Issue: https://github.com/jaywcjlove/mocker-api/issues/50
22 | */
23 | let contentType = req.get('Content-Type');
24 | contentType = contentType && contentType.replace(/;.*$/, '');
25 | if(options.bodyParserConf && options.bodyParserConf[contentType]) {
26 | // 如果存在options.bodyParserConf配置 {'text/plain': 'text','text/html': 'text'}
27 | switch(options.bodyParserConf[contentType]){// 获取bodyParser的方法
28 | case 'raw': bodyParserMethd = bodyParser.raw({...options.bodyParserRaw }); break;
29 | case 'text': bodyParserMethd = bodyParser.text({...options.bodyParserText }); break;
30 | case 'urlencoded': bodyParserMethd = bodyParser.urlencoded({extended: false, ...options.bodyParserUrlencoded }); break;
31 | case 'json': bodyParserMethd = bodyParser.json({ ...options.bodyParserJSON });//使用json解析 break;
32 | }
33 | } else {
34 | // 兼容原来的代码,默认解析
35 | // Compatible with the original code, default parsing
36 | switch(contentType){
37 | case 'text/plain': bodyParserMethd = bodyParser.raw({...options.bodyParserRaw }); break;
38 | case 'text/html': bodyParserMethd = bodyParser.text({...options.bodyParserText }); break;
39 | case 'application/x-www-form-urlencoded': bodyParserMethd = bodyParser.urlencoded({extended: false, ...options.bodyParserUrlencoded }); break;
40 | }
41 | }
42 |
43 | bodyParserMethd(req, res, function () {
44 | const result = mocker[mockerKey];
45 | if (typeof result === 'function') {
46 | const rgxStr = ~mockerKey.indexOf(' ') ? ' ' : '';
47 | req.params = pathMatch({ sensitive: false, trailing: false, end: false })(mockerKey.split(new RegExp(rgxStr))[1])(URL.parse(req.url).pathname);
48 | result(req, res, next);
49 | } else {
50 | res.json(result);
51 | }
52 | });
53 | }
--------------------------------------------------------------------------------
/example/create-react-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/typescript/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 | const user = require('./user.mock');
3 |
4 | // 是否禁用代理
5 | const noProxy = process.env.NO_PROXY === 'true';
6 |
7 | const proxy = {
8 | ...user,
9 | // 'GET /api/userinfo/:id': (req, res) => {
10 | // console.log('---->', req.params)
11 | // return res.json({
12 | // id: 1,
13 | // username: 'kenny',
14 | // sex: 6
15 | // });
16 | // },
17 | 'GET /api/user/list/:id/:type': (req, res) => {
18 | const { type } = req.params;
19 | console.log('req.params:', req.params);
20 | if (type === 'webpack') {
21 | return res.status(403).json({
22 | status: 'error',
23 | code: 403
24 | });
25 | }
26 | return res.json([
27 | {
28 | id: 1,
29 | username: 'kenny',
30 | sex: 6
31 | }, {
32 | id: 2,
33 | username: 'kenny',
34 | sex: 6
35 | }
36 | ]);
37 | },
38 | 'GET /api/:first': (req, res) => {
39 | console.log(req.params); // { first: 'something' }
40 | return res.json({ test: false });
41 | },
42 | 'GET /api/:first/items/:second': (req, res) => {
43 | console.log(req.params); // false
44 | return res.json({ test: true });
45 | },
46 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => {
47 | console.log(req.params); // false
48 | return res.json({ test: true, path: req.params.path });
49 | },
50 | 'GET /repos/hello': (req, res) => {
51 | return res.json({
52 | text: 'this is from mock server'
53 | });
54 | },
55 |
56 | 'GET /api/jobs/:id': (req, res) => {
57 | return res.json({
58 | text: 'url: /api/jobs/:id'
59 | });
60 | },
61 |
62 | 'GET /api/jobs': (req, res) => {
63 | return res.json({
64 | text: 'url: /api/jobs'
65 | });
66 | },
67 | 'POST /api/login/account': (req, res) => {
68 | const { password, username } = req.body;
69 | if (password === '888888' && username === 'admin') {
70 | return res.json({
71 | status: 'ok',
72 | code: 0,
73 | token: "sdfsdfsdfdsf",
74 | data: {
75 | id: 1,
76 | username: 'kenny',
77 | sex: 6
78 | }
79 | });
80 | } else {
81 | return res.json({
82 | status: 'error',
83 | code: 403
84 | });
85 | }
86 | },
87 | 'DELETE /api/user/:id': (req, res) => {
88 | console.log('---->', req.body)
89 | console.log('---->', req.params.id)
90 | res.send({ status: 'ok', message: '删除成功!' });
91 | },
92 | }
93 | module.exports = (noProxy ? {} : delay(proxy, 1000));
94 | // module.exports = proxy;
--------------------------------------------------------------------------------
/example/base/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 | const user = require('./user.mock');
3 |
4 | // 是否禁用代理
5 | const noProxy = process.env.NO_PROXY === 'true';
6 |
7 | const proxy = {
8 | ...user,
9 | // 'GET /api/userinfo/:id': (req, res) => {
10 | // console.log('---->', req.params)
11 | // return res.json({
12 | // id: 1,
13 | // username: 'kenny',
14 | // sex: 6
15 | // });
16 | // },
17 | 'GET /api/user/list/:id/:type': (req, res) => {
18 | const { type } = req.params;
19 | console.log('req.params:', req.params);
20 | if (type === 'webpack') {
21 | return res.status(403).json({
22 | status: 'error',
23 | code: 403
24 | });
25 | }
26 | return res.json([
27 | {
28 | id: 1,
29 | username: 'kenny',
30 | sex: 6
31 | }, {
32 | id: 2,
33 | username: 'kenny',
34 | sex: 6
35 | }
36 | ]);
37 | },
38 | 'GET /api/:first': (req, res) => {
39 | console.log(req.params); // { first: 'something' }
40 | return res.json({ test: false });
41 | },
42 | 'GET /api/:first/items/:second': (req, res) => {
43 | console.log(req.params); // false
44 | return res.json({ test: true });
45 | },
46 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => {
47 | console.log(req.params); // false
48 | return res.json({ test: true, path: req.params.path });
49 | },
50 | 'GET /repos/hello': (req, res) => {
51 | return res.json({
52 | text: 'this is from mock server'
53 | });
54 | },
55 |
56 | 'GET /api/jobs/:id': (req, res) => {
57 | console.log('---->', req.params)
58 | return res.json({
59 | text: `url: /api/jobs/${req.params.id}`
60 | });
61 | },
62 |
63 | 'GET /api/jobs': (req, res) => {
64 | return res.json({
65 | text: 'url: /api/jobs'
66 | });
67 | },
68 | 'POST /api/login/account': (req, res) => {
69 | const { password, username } = req.body;
70 | if (password === '888888' && username === 'admin') {
71 | return res.json({
72 | status: 'ok',
73 | code: 0,
74 | token: "sdfsdfsdfdsf",
75 | data: {
76 | id: 1,
77 | username: 'kenny',
78 | sex: 6
79 | }
80 | });
81 | } else {
82 | return res.json({
83 | status: 'error',
84 | code: 403
85 | });
86 | }
87 | },
88 | 'DELETE /api/user/:id': (req, res) => {
89 | console.log('---->', req.body)
90 | console.log('---->', req.params.id)
91 | res.send({ status: 'ok', message: '删除成功!' });
92 | },
93 | }
94 | module.exports = (noProxy ? {} : delay(proxy, 1000));
95 | // module.exports = proxy;
--------------------------------------------------------------------------------
/example/webpack/mocker/index.js:
--------------------------------------------------------------------------------
1 | const delay = require('mocker-api/delay');
2 | const { login } = require('./user');
3 |
4 | // 是否禁用代理
5 | const noProxy = process.env.NO_PROXY === 'true';
6 |
7 | const proxy = {
8 | // Priority processing.
9 | _proxy: {
10 | priority: 'mocker',
11 | proxy: {
12 | '/repos/(.*)': 'https://api.github.com/',
13 | // '/repos/jaywcjlove/webpack-api-mocker': 'https://api.github.com/repos/jaywcjlove/webpack-api-mocker',
14 | },
15 | changeHost: true,
16 | },
17 | 'GET /repos/jaywcjlove/webpack-api-mocker': {
18 | "stargazers_count": 11111,
19 | "subscribers_count": 6
20 | },
21 | 'GET /repos/jaywcjlove/github-rank': {
22 | "stargazers_count": 55555555,
23 | "subscribers_count": 6
24 | },
25 | 'GET /api/userinfo/:id': (req, res) => {
26 | console.log('-1--->', req.params)
27 | return res.json({
28 | id: req.params.id,
29 | username: 'kenny',
30 | sex: 6
31 | });
32 | },
33 | 'GET /api/:owner/:repo/raw/:ref/(.*)': (req, res) => {
34 | const { owner, repo, ref } = req.params;
35 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md
36 | // owner => admin
37 | // repo => webpack-mock-api
38 | // ref => master
39 | // req.params[0] => add/ddd.md
40 | return res.json({
41 | id: 1,
42 | owner, repo, ref,
43 | path: req.params[0]
44 | });
45 | },
46 | 'GET /api/user/list/:id/:type': (req, res) => {
47 | const { type } = req.params;
48 | if (type === 'webpack') {
49 | return res.status(403).json({
50 | status: 'error',
51 | code: 403
52 | });
53 | }
54 | return res.json([
55 | {
56 | id: 1,
57 | username: 'kenny',
58 | sex: 6
59 | }, {
60 | id: 2,
61 | username: 'kenny',
62 | sex: 6
63 | }
64 | ]);
65 | },
66 | 'GET /repos/hello': (req, res) => {
67 | console.log('/repos/hello:=>>>', req.params)
68 | return res.json({
69 | text: 'this is from mock server'
70 | });
71 | },
72 |
73 | 'GET /api/jobs/:id': (req, res) => {
74 | return res.json({
75 | text: 'url: /api/jobs/:id'
76 | });
77 | },
78 |
79 | 'GET /api/jobs': (req, res) => {
80 | return res.json({
81 | text: 'url: /api/jobs'
82 | });
83 | },
84 | 'POST /api/login/account': login,
85 | // 'POST /api/login/account': (req, res) => {
86 | // const { password, username } = req.body;
87 | // if (password === '888888' && username === 'admin') {
88 | // return res.json({
89 | // status: 'ok',
90 | // code: 0,
91 | // token: "sdfsdfsdfdsf",
92 | // data: {
93 | // id: 1,
94 | // username: 'kenny',
95 | // sex: 6
96 | // }
97 | // });
98 | // } else {
99 | // return res.json({
100 | // status: 'error',
101 | // code: 403
102 | // });
103 | // }
104 | // },
105 | 'DELETE /api/user/:id': (req, res) => {
106 | console.log('--2-->', req.body)
107 | console.log('--3-->', req.params.id)
108 | res.send({ status: 'ok', message: '删除成功!' });
109 | }
110 | }
111 | module.exports = (noProxy ? {} : delay(proxy, 1000));
112 | // module.exports = proxy;
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | build-deploy:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: write
12 | id-token: write
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: 20
18 | registry-url: 'https://registry.npmjs.org'
19 |
20 | - name: Look Changelog
21 | uses: jaywcjlove/changelog-generator@main
22 | with:
23 | token: ${{ secrets.GITHUB_TOKEN }}
24 | filter-author: (小弟调调™|Renovate Bot)
25 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}'
26 |
27 | - run: yarn install
28 | - run: yarn run build
29 | - run: yarn run coverage
30 | - run: yarn run doc
31 |
32 | - run: npm i markdown-to-html-cli -g
33 | - run: markdown-to-html -s packages/core/README.md --output packages/core/docs/index.html
34 | - run: markdown-to-html -s README-zh.md --output packages/core/docs/index.zh.html
35 |
36 | - run: npm run example:create-react-app
37 | - run: npm run example:typescript
38 | - run: npm run example:webpack
39 |
40 | - name: Generate Contributors Images
41 | uses: jaywcjlove/github-action-contributors@main
42 | with:
43 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
44 | output: ./packages/core/docs/CONTRIBUTORS.svg
45 | avatarSize: 43
46 |
47 | - name: Is a tag created auto?
48 | id: create_tag
49 | uses: jaywcjlove/create-tag-action@main
50 | with:
51 | package-path: ./packages/core/package.json
52 |
53 | - name: get tag version
54 | id: tag_version
55 | uses: jaywcjlove/changelog-generator@main
56 |
57 | - name: Deploy
58 | uses: peaceiris/actions-gh-pages@v4
59 | with:
60 | commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }}
61 | github_token: ${{ secrets.GITHUB_TOKEN }}
62 | publish_dir: ./packages/core/docs
63 |
64 | - name: Generate Changelog
65 | id: changelog
66 | uses: jaywcjlove/changelog-generator@main
67 | if: steps.create_tag.outputs.successful
68 | with:
69 | head-ref: ${{ steps.create_tag.outputs.version }}
70 | filter-author: (小弟调调™|Renovate Bot)
71 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}'
72 |
73 | - name: Create Release
74 | uses: ncipollo/release-action@v1
75 | if: steps.create_tag.outputs.successful
76 | with:
77 | allowUpdates: true
78 | token: ${{ secrets.GITHUB_TOKEN }}
79 | name: ${{ steps.create_tag.outputs.version }}
80 | tag: ${{ steps.create_tag.outputs.version }}
81 | body: |
82 | [](https://jaywcjlove.github.io/#/sponsor) [](https://uiwjs.github.io/npm-unpkg/#/pkg/mocker-api@${{steps.create_tag.outputs.versionNumber}}/file/README.md) [](https://www.npmjs.com/package/mocker-api) [](https://bundlephobia.com/result?p=mocker-api@${{steps.create_tag.outputs.versionNumber}})
83 |
84 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/mocker-api/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html
85 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }}
86 |
87 | ```bash
88 | npm i mocker-api@${{steps.create_tag.outputs.versionNumber}}
89 | ```
90 |
91 | ${{ steps.changelog.outputs.changelog }}
92 |
93 | - run: npm publish --access public --provenance
94 | continue-on-error: true
95 | working-directory: packages/core
96 | env:
97 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
98 |
99 | # - uses: JS-DevTools/npm-publish@v1
100 | # with:
101 | # token: ${{ secrets.NPM_TOKEN }}
102 | # package: ./packages/core/package.json
103 |
104 | # - run: npm install @jsdevtools/npm-publish -g
105 | # - run: npm-publish --token="${{ secrets.NPM_TOKEN }}" ./packages/core/package.json
106 |
--------------------------------------------------------------------------------
/packages/core/src/bin/mocker.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import path from 'path';
3 | import { existsSync } from 'fs';
4 | import prepareUrls from 'local-ip-url/prepareUrls';
5 | import detect from 'detect-port';
6 | import color from 'colors-cli/safe';
7 | import express from 'express';
8 | import minimist from 'minimist';
9 | import apiMocker, { MockerOption } from '../';
10 |
11 | interface MockerConfig extends MockerOption {
12 | host: string;
13 | port: number;
14 | }
15 |
16 | const CWD = process.cwd();
17 | const PKG_PATH = path.resolve(CWD, './package.json');
18 | const DEFAULT_MOCKER_CONFIG_PATH = path.resolve(CWD, './mocker.config.json');
19 | const DEFAULT_MOCK_PATH = ['./mock'];
20 | const DEFAULT_CONFIG: MockerConfig = {
21 | host: '0.0.0.0',
22 | port: 3721
23 | };
24 |
25 | (async () => {
26 | const argvs = minimist(process.argv.slice(2));
27 |
28 | if (argvs.h || argvs.help) {
29 | console.log('\n Usage: mocker [--config] [--help|h]')
30 | console.log('\n Displays help information.')
31 | console.log('\n Options:')
32 | console.log(' --config ', 'Simple configuration')
33 | console.log('\n Example:')
34 | console.log(' mocker mock/index.js')
35 | console.log(' mocker mock/index.js --port 7788')
36 | console.log(' mocker mock/index.js --host 0.0.0.0')
37 | console.log(' mocker mock/m1.js test/m2.js')
38 | console.log(' mocker mock/m1.js --config mocker.config.json')
39 | console.log('\n');
40 | return;
41 | }
42 | // Fix type errors
43 | const { version } = require('../../package.json');
44 |
45 | if (argvs.v || argvs.version) {
46 | console.log(version);
47 | return
48 | }
49 |
50 | const paths = argvs['_'];
51 |
52 | if (paths.length === 0) {
53 | console.log(color.red('Error: Need to pass parameters!'));
54 | console.log(`E.g: ${color.yellow('mocker ')}\n`);
55 | return;
56 | }
57 |
58 | const entryFiles = paths || DEFAULT_MOCK_PATH;
59 |
60 | let mockConfigPath = argvs.config || DEFAULT_MOCKER_CONFIG_PATH;
61 | let mockerConfig = DEFAULT_CONFIG;
62 |
63 | if (argvs.config) {
64 | mockConfigPath = argvs.config;
65 | }
66 |
67 | if (!existsSync(path.resolve(mockConfigPath))) {
68 | mockerConfig.host = process.env.HOST ? process.env.HOST : mockerConfig.host;
69 | mockerConfig.port = await detect(mockerConfig.port);
70 | } else {
71 | mockerConfig = require(path.resolve(mockConfigPath));
72 | }
73 |
74 | /**
75 | * Support setting configuration on package.json
76 | * https://github.com/jaywcjlove/mocker-api/issues/144
77 | */
78 | if (existsSync(PKG_PATH)) {
79 | const pkgConf = require(PKG_PATH);
80 | if (pkgConf.mocker) {
81 | Object.assign(mockerConfig, pkgConf.mocker);
82 | }
83 | }
84 |
85 | if (argvs.host) {
86 | mockerConfig.host = argvs.host;
87 | }
88 |
89 | if (argvs.port) {
90 | mockerConfig.port = argvs.port;
91 | }
92 |
93 | const DEFAULT_PORT = mockerConfig.port;
94 | const DEFAULT_HOST = mockerConfig.host;
95 | const app = express();
96 |
97 | app.all('/*', (req, res, next) => {
98 | console.log(`${color.green(req.method)} - ${req.url}`);
99 | res.header('Access-Control-Allow-Origin', '*');
100 | res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length,Authorization,Accept,X-Requested-With');
101 | res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
102 | next();
103 | });
104 |
105 | delete mockerConfig.port;
106 | delete mockerConfig.host;
107 | apiMocker(app, entryFiles, { ...mockerConfig });
108 |
109 | app.listen(DEFAULT_PORT, () => {
110 | const localIpUrl = prepareUrls({
111 | protocol: 'http',
112 | host: DEFAULT_HOST,
113 | port: DEFAULT_PORT,
114 | });
115 | console.log(`> Server Listening at Local: ${color.green(localIpUrl.localUrl)}`);
116 | console.log(`> On Your Network: ${color.green(localIpUrl.lanUrl)}\n`);
117 | });
118 | /**
119 | * Event listener for HTTP server "error" event.
120 | */
121 | app.on('error', (error: any) => {
122 | if (error.syscall !== 'listen') {
123 | throw error;
124 | }
125 | const bind = typeof DEFAULT_PORT === 'string' ? `Pipe ${DEFAULT_PORT}` : `Port ${DEFAULT_PORT}`;
126 | // handle specific listen errors with friendly messages
127 | switch (error.code) {
128 | case 'EACCES':
129 | console.error(`${bind} requires elevated privileges`); // eslint-disable-line
130 | process.exit(1);
131 | break;
132 | case 'EADDRINUSE':
133 | console.error(`${bind} is already in use`); // eslint-disable-line
134 | process.exit(1);
135 | break;
136 | default:
137 | throw error;
138 | }
139 | });
140 | })();
141 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | English ·
30 | 快速开始 ·
31 | 使用 ·
32 | 参数设置 ·
33 | 延迟响应 ·
34 | 实例 ·
35 | License ·
36 | Type
37 |
38 |
39 | `mocker-api` 用于创建 REST API 的模拟。当你需要在没有实际 REST API 服务器的情况下测试你的应用程序时,它会非常有用。
40 |
41 | **特点:**
42 |
43 | 🔥 内置对热替换 Mocker 文件的支持。
44 | 🚀 通过 JSON 快速轻松地配置 API。
45 | 🌱 简化的 Mock API 代理。
46 | 💥 可以独立使用,不依赖于 [webpack](https://github.com/webpack/webpack) 和 [webpack-dev-server](https://github.com/webpack/webpack-dev-server)。
47 |
48 | ## 快速开始
49 |
50 | ```bash
51 | mkdir mocker-app && cd mocker-app
52 |
53 | # 根据规则创建模拟程序配置文件
54 | touch api.js
55 |
56 | # 全局安装。
57 | npm install mocker-api -g
58 | # 运行服务
59 | mocker ./api.js
60 |
61 | # Run server at localhost:8000
62 | mocker ./api.js --host localhost --port 8000
63 | ```
64 |
65 | ## 安装
66 |
67 | 您可以将其 `package.json` 配置作为当前项目依赖项。
68 |
69 | ```bash
70 | npm install mocker-api --save-dev
71 | ```
72 |
73 | ## 使用
74 |
75 | `mocker-api` 支持模拟,在 `mocker / index.js` 中配置。
76 |
77 | 您可以修改 [http-proxy](https://www.npmjs.com/package/http-proxy) 选项并通过添加 `httpProxy` 配置来添加事件监听器
78 |
79 | ```js
80 | const proxy = {
81 | // 优先处理。
82 | // apiMocker(app, path, option)
83 | // 这是 apiMocker 的选项参数设置 `option`
84 | _proxy: {
85 | proxy: {
86 | // 将路径字符串(例如`/user/:name`)转换为正则表达式。
87 | // https://www.npmjs.com/package/path-to-regexp
88 | '/repos/(.*)': 'https://api.github.com/',
89 | '/:owner/:repo/raw/:ref/(.*)': 'http://127.0.0.1:2018',
90 | '/api/repos/(.*)': 'http://127.0.0.1:3721/'
91 | },
92 | // 重写目标网址路径。对象键将用作RegEx来匹配路径。
93 | // https://github.com/jaywcjlove/mocker-api/issues/62
94 | pathRewrite: {
95 | '^/api/repos/': '/repos/',
96 | },
97 | changeHost: true,
98 | // 修改 http-proxy 选项
99 | httpProxy: {
100 | options: {
101 | ignorePath: true,
102 | },
103 | listeners: {
104 | proxyReq: function (proxyReq, req, res, options) {
105 | console.log('proxyReq');
106 | },
107 | },
108 | },
109 | },
110 | // =====================
111 | // 默认的 GET 请求。
112 | // https://github.com/jaywcjlove/mocker-api/pull/63
113 | '/api/user': {
114 | id: 1,
115 | username: 'kenny',
116 | sex: 6
117 | },
118 | 'GET /api/user': {
119 | id: 1,
120 | username: 'kenny',
121 | sex: 6
122 | },
123 | 'GET /api/user/list': [
124 | {
125 | id: 1,
126 | username: 'kenny',
127 | sex: 6
128 | }, {
129 | id: 2,
130 | username: 'kenny',
131 | sex: 6
132 | }
133 | ],
134 | 'GET /api/:owner/:repo/raw/:ref/(.*)': (req, res) => {
135 | const { owner, repo, ref } = req.params;
136 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md
137 | // owner => admin
138 | // repo => webpack-mock-api
139 | // ref => master
140 | // req.params[0] => add/ddd.md
141 | return res.json({
142 | id: 1,
143 | owner, repo, ref,
144 | path: req.params[0]
145 | });
146 | },
147 | 'POST /api/login/account': (req, res) => {
148 | const { password, username } = req.body;
149 | if (password === '888888' && username === 'admin') {
150 | return res.json({
151 | status: 'ok',
152 | code: 0,
153 | token: "sdfsdfsdfdsf",
154 | data: {
155 | id: 1,
156 | username: 'kenny',
157 | sex: 6
158 | }
159 | });
160 | } else {
161 | return res.status(403).json({
162 | status: 'error',
163 | code: 403
164 | });
165 | }
166 | },
167 | 'DELETE /api/user/:id': (req, res) => {
168 | console.log('---->', req.body)
169 | console.log('---->', req.params.id)
170 | res.send({ status: 'ok', message: '删除成功!' });
171 | }
172 | }
173 | module.exports = proxy;
174 | ```
175 |
176 | ## 参数设置
177 |
178 | - [`proxy`](https://www.npmjs.com/package/path-to-regexp) => `{}` Proxy settings, Turn a path string such as `/user/:name` into a regular expression.
179 | - [`pathRewrite`](https://github.com/jaywcjlove/mocker-api/issues/62) => `{}` rewrite target's url path. Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62)
180 | - `withFullUrlPath=false` => `Boolean` the proxy regular expression support full url path. if the proxy regular expression like `/test?a=1&b=1` can be matched. [#25](https://github.com/jaywcjlove/mocker-api/issues/25)
181 | - `priority` => `proxy` priority `proxy` or `mocker` [#151](https://github.com/jaywcjlove/mocker-api/issues/151)
182 | - `changeHost` => `Boolean` Setting req headers host.
183 | - `httpProxy` => `{}` Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy)
184 | - [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser
185 | - [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser
186 | - [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser
187 | - [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser
188 | - `bodyParserConf` => `{}` bodyParser settings. eg: `bodyParserConf : {'text/plain': 'text','text/html': 'text'}` will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text`
189 | - [`watchOptions`](https://github.com/paulmillr/chokidar#api) => `object` Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api)
190 | - `header` => `{}` Access Control Allow options.
191 | ```js
192 | {
193 | header: {
194 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
195 | }
196 | }
197 | ```
198 |
199 | ⚠️ No wildcard asterisk ~~`*`~~ - use parameters instead `(.*)`, support `v1.7.3+`
200 |
201 | ## 延迟响应
202 |
203 | 您可以使用功能性工具来增强模拟效果。[#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17)
204 |
205 | ```js
206 | const delay = require('mocker-api/delay');
207 | const noProxy = process.env.NO_PROXY === 'true';
208 |
209 | const proxy = {
210 | 'GET /api/user': {
211 | id: 1,
212 | username: 'kenny',
213 | sex: 6
214 | },
215 | // ...
216 | }
217 | module.exports = (noProxy ? {} : delay(proxy, 1000));
218 | ```
219 |
220 | ## apiMocker
221 |
222 | ```js
223 | apiMocker(app, mockerFilePath[, options])
224 | apiMocker(app, Mocker[, options])
225 | ```
226 |
227 | 多个模拟文件监听
228 |
229 | ```js
230 | const apiMocker = require('mocker-api');
231 | const mockerFile = ['./mock/index.js'];
232 | // or
233 | // const mockerFile = './mock/index.js';
234 | apiMocker(app, mockerFile, options)
235 | ```
236 |
237 | ## 实例
238 |
239 | ### 在命令行中使用
240 |
241 | [Base example](example/base)
242 |
243 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server).
244 |
245 | ```bash
246 | # Global install dependent.
247 | npm install mocker-api -g
248 | # Run server
249 | mocker ./mocker/index.js
250 | ```
251 |
252 | Or you can put it the `package.json` config as a current project dependency.
253 |
254 | ```diff
255 | {
256 | "name": "base-example",
257 | "scripts": {
258 | + "api": "mocker ./mocker"
259 | },
260 | "devDependencies": {
261 | + "mocker-api": "2.9.5"
262 | },
263 | "license": "MIT"
264 | }
265 | ```
266 |
267 | ### 在 [Express](https://github.com/expressjs/express) 中使用
268 |
269 | [Express example](https://github.com/jaywcjlove/mocker-api/tree/master/example/express)
270 |
271 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server).
272 |
273 | ```diff
274 | const express = require('express');
275 | + const path = require('path');
276 | + const apiMocker = require('mocker-api');
277 |
278 | const app = express();
279 |
280 | + apiMocker(app, path.resolve('./mocker/index.js'))
281 | app.listen(8080);
282 | ```
283 |
284 | or
285 |
286 | ```diff
287 | const express = require('express');
288 | + const apiMocker = require('mocker-api');
289 |
290 | const app = express();
291 |
292 | + apiMocker(app, {
293 | + 'GET /api/user': {
294 | + id: 1,
295 | + sex: 0
296 | + }
297 | + });
298 |
299 | app.listen(8080);
300 | ```
301 |
302 | ### 在 [Webpack](https://github.com/webpack/webpack) 中使用
303 |
304 | [webpack example](https://github.com/jaywcjlove/mocker-api/tree/master/example/webpack)
305 |
306 | To use api mocker on your [Webpack](https://github.com/webpack/webpack) projects, simply add a setup options to your [webpack-dev-server](https://github.com/webpack/webpack-dev-server) options:
307 |
308 | Change your config file to tell the dev server where to look for files: `webpack.config.js`.
309 |
310 | ```diff
311 | const HtmlWebpackPlugin = require('html-webpack-plugin');
312 | + const path = require('path');
313 | + const apiMocker = require('mocker-api');
314 |
315 | module.exports = {
316 | entry: {
317 | app: './src/index.js',
318 | },
319 | output: {
320 | filename: '[name].bundle.js',
321 | path: path.resolve(__dirname, 'dist')
322 | },
323 | + devServer: {
324 | + ...
325 | + before(app){
326 | + apiMocker(app, path.resolve('./mocker/index.js'), {
327 | + proxy: {
328 | + '/repos/*': 'https://api.github.com/',
329 | + '/:owner/:repo/raw/:ref/*': 'http://127.0.0.1:2018'
330 | + },
331 | + changeHost: true,
332 | + })
333 | + }
334 | + },
335 | plugins: [
336 | new HtmlWebpackPlugin({
337 | template: path.resolve('./public/index.html'),
338 | title: 'Webpack App Mocker API'
339 | })
340 | ],
341 | };
342 | ```
343 |
344 | Must have a file suffix! For example: `./mocker/index.js`.
345 |
346 | Let's add a script to easily run the dev server as well: `package.json`
347 |
348 | ```diff
349 | {
350 | "name": "development",
351 | "version": "1.0.0",
352 | "description": "",
353 | "main": "webpack.config.js",
354 | "scripts": {
355 | "test": "echo \"Error: no test specified\" && exit 1",
356 | + "start": "webpack serve --progress --mode development",
357 | "build": "webpack --mode production"
358 | },
359 | "keywords": [],
360 | "author": "",
361 | "license": "MIT",
362 | "devDependencies": {
363 | "html-webpack-plugin": "4.5.0",
364 | "mocker-api": "2.9.5",
365 | "webpack": "5.22.0",
366 | "webpack-cli": "4.5.0",
367 | "webpack-dev-server": "3.11.2"
368 | }
369 | }
370 | ```
371 |
372 | Mock API proxying made simple.
373 |
374 | ```diff
375 | {
376 | before(app){
377 | + apiMocker(app, path.resolve('./mocker/index.js'), {
378 | + proxy: {
379 | + '/repos/*': 'https://api.github.com/',
380 | + },
381 | + changeHost: true,
382 | + })
383 | }
384 | }
385 | ```
386 |
387 | ### 在 create-react-app 中使用
388 |
389 | [create-react-app example](https://github.com/jaywcjlove/mocker-api/tree/master/example/create-react-app)
390 |
391 | 创建 [`src/setupProxy.js`](https://github.com/jaywcjlove/mocker-api/blob/64a093685b05c70ab0ddcf3fd5dbede7871efa8a/example/create-react-app/src/setupProxy.js#L1-L11) 并放置以下内容:
392 |
393 | ```diff
394 | + const apiMocker = require('mocker-api');
395 | + const path = require('path');
396 |
397 | module.exports = function(app) {
398 | + apiMocker(app, path.resolve('./mocker/index.js'), {
399 | + proxy: {
400 | + '/repos/(.*)': 'https://api.github.com/',
401 | + },
402 | + changeHost: true,
403 | + });
404 | };
405 | ```
406 |
407 | ```diff
408 | {
409 | .....
410 | "devDependencies": {
411 | + "mocker-api": "2.9.5"
412 | },
413 | ....
414 | }
415 | ```
416 |
417 |
418 | ### 开发
419 |
420 | ```shell
421 | $ yarn install
422 | $ yarn run build
423 | $ yarn run watch
424 | $ yarn run test
425 | ```
426 |
427 | ## Contributors
428 |
429 | As always, thanks to our amazing contributors!
430 |
431 |
432 |
433 |
434 |
435 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors).
436 |
437 | ## License
438 |
439 | [MIT © Kenny Wong](https://github.com/jaywcjlove/mocker-api/blob/master/LICENSE)
440 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import URL from 'url';
2 | import PATH from 'path';
3 | import * as net from "net";
4 | import * as http from "http";
5 | import { Request, Response, NextFunction, Application } from 'express';
6 | import bodyParser from 'body-parser';
7 | import httpProxy from 'http-proxy';
8 | import * as toRegexp from 'path-to-regexp';
9 | import clearModule from 'clear-module';
10 | import chokidar, { ChokidarOptions } from 'chokidar';
11 | import color from 'colors-cli/safe';
12 | import { proxyHandle } from './proxyHandle';
13 | import { mockerHandle } from './mockerHandle';
14 |
15 | export * from './delay';
16 | export * from './utils';
17 |
18 | export type ProxyTargetUrl = string | Partial;
19 | export type MockerResultFunction = ((req: Request, res: Response, next?: NextFunction) => void);
20 | export type MockerResult = string | number| Array | Record | MockerResultFunction;
21 |
22 | /**
23 | * Setting a proxy router.
24 | * @example
25 | *
26 | * ```json
27 | * {
28 | * '/api/user': {
29 | * id: 1,
30 | * username: 'kenny',
31 | * sex: 6
32 | * },
33 | * 'DELETE /api/user/:id': (req, res) => {
34 | * res.send({ status: 'ok', message: '删除成功!' });
35 | * }
36 | * }
37 | * ```
38 | */
39 | export type MockerProxyRoute = Record & {
40 | /**
41 | * This is the option parameter setting for apiMocker
42 | * Priority processing.
43 | * apiMocker(app, path, option)
44 | * {@link MockerOption}
45 | */
46 | _proxy?: MockerOption;
47 | }
48 |
49 | /**
50 | * Listening for proxy events.
51 | * This options contains listeners for [node-http-proxy](https://github.com/http-party/node-http-proxy#listening-for-proxy-events).
52 | * {typeof httpProxy.on}
53 | * {@link httpProxy}
54 | */
55 | export interface HttpProxyListeners extends Record {
56 | start?: (
57 | req: http.IncomingMessage,
58 | res: http.ServerResponse,
59 | target: ProxyTargetUrl
60 | ) => void;
61 | proxyReq?: (
62 | proxyReq: http.ClientRequest,
63 | req: http.IncomingMessage,
64 | res: http.ServerResponse,
65 | options: httpProxy.ServerOptions
66 | ) => void;
67 | proxyRes?: (
68 | proxyRes: http.IncomingMessage,
69 | req: http.IncomingMessage,
70 | res: http.ServerResponse
71 | ) => void;
72 | proxyReqWs?: (
73 | proxyReq: http.ClientRequest,
74 | req: http.IncomingMessage,
75 | socket: net.Socket,
76 | options: httpProxy.ServerOptions,
77 | head: any
78 | ) => void;
79 | econnreset?: (
80 | err: Error,
81 | req: http.IncomingMessage,
82 | res: http.ServerResponse,
83 | target: ProxyTargetUrl
84 | ) => void
85 | end?: (
86 | req: http.IncomingMessage,
87 | res: http.ServerResponse,
88 | proxyRes: http.IncomingMessage
89 | ) => void;
90 | /**
91 | * This event is emitted once the proxy websocket was closed.
92 | */
93 | close?: (
94 | proxyRes: http.IncomingMessage,
95 | proxySocket: net.Socket,
96 | proxyHead: any
97 | ) => void;
98 | }
99 |
100 | export interface MockerOption {
101 | /**
102 | * priority 'proxy' or 'mocker'
103 | * @default `proxy`
104 | * @issue [#151](https://github.com/jaywcjlove/mocker-api/issues/151)
105 | */
106 | priority?: 'proxy' | 'mocker';
107 | /**
108 | * `Boolean` Setting req headers host.
109 | */
110 | changeHost?: boolean;
111 | /**
112 | * rewrite target's url path.
113 | * Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62)
114 | * @default `{}`
115 | */
116 | pathRewrite?: Record,
117 | /**
118 | * Proxy settings, Turn a path string such as `/user/:name` into a regular expression. [path-to-regexp](https://www.npmjs.com/package/path-to-regexp)
119 | * @default `{}`
120 | */
121 | proxy?: Record,
122 | /**
123 | * Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy)
124 | * @default `{}`
125 | */
126 | httpProxy?: {
127 | options?: httpProxy.ServerOptions;
128 | listeners?: HttpProxyListeners
129 | };
130 | /**
131 | * bodyParser settings.
132 | * @example
133 | *
134 | * ```js
135 | * bodyParser = {"text/plain": "text","text/html": "text"}
136 | * ```
137 | *
138 | * will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text`
139 | *
140 | * @default `{}`
141 | */
142 | bodyParserConf?: {
143 | [key: string]: 'raw' | 'text' | 'urlencoded' | 'json';
144 | };
145 | /**
146 | * [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser
147 | * @default `{}`
148 | */
149 | bodyParserJSON?: bodyParser.OptionsJson;
150 | /**
151 | * [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser
152 | * @default `{}`
153 | */
154 | bodyParserText?: bodyParser.OptionsText;
155 | /**
156 | * [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser
157 | * @default `{}`
158 | */
159 | bodyParserRaw?: bodyParser.Options;
160 | /**
161 | * [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser
162 | * @default `{}`
163 | */
164 | bodyParserUrlencoded?: bodyParser.OptionsUrlencoded;
165 | /**
166 | * Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api)
167 | * @default `{}`
168 | */
169 | watchOptions?: ChokidarOptions;
170 | /**
171 | * Access Control Allow options.
172 | * @default `{}`
173 | * @example
174 | * ```js
175 | * {
176 | * header: {
177 | * 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
178 | * }
179 | * }
180 | * ```
181 | */
182 | header?: Record,
183 | /**
184 | * `Boolean` the proxy regular expression support full url path.
185 | * if the proxy regular expression like /test?a=1&b=1 can be matched
186 | */
187 | withFullUrlPath?: boolean
188 | }
189 |
190 | const pathToRegexp = toRegexp.pathToRegexp;
191 | let mocker: MockerProxyRoute = {};
192 |
193 | module.exports = mockerApi
194 |
195 | export default function mockerApi(app: Application, watchFile: string | string[] | MockerProxyRoute, conf: MockerOption = {}) {
196 | const watchFiles = (Array.isArray(watchFile) ? watchFile : typeof watchFile === 'string' ? [watchFile] : []).map(str => PATH.resolve(str));
197 |
198 | if (watchFiles.some(file => !file)) {
199 | throw new Error('Mocker file does not exist!.');
200 | }
201 |
202 | /**
203 | * Mybe watch file or pass parameters
204 | * https://github.com/jaywcjlove/mocker-api/issues/116
205 | */
206 | const isWatchFilePath = (Array.isArray(watchFile) && watchFile.every(val => typeof val === 'string')) || typeof watchFile === 'string';
207 | mocker = isWatchFilePath ? getConfig() : watchFile;
208 |
209 | if (!mocker) {
210 | return (req: Request, res: Response, next: NextFunction) => {
211 | next();
212 | }
213 | }
214 | let options: MockerOption = {...conf, ...(mocker._proxy || {})}
215 | const defaultOptions: MockerOption = {
216 | changeHost: true,
217 | pathRewrite: {},
218 | proxy: {},
219 | // proxy: proxyConf: {},
220 | httpProxy: {},
221 | // httpProxy: httpProxyConf: {},
222 | bodyParserConf: {},
223 | bodyParserJSON: {},
224 | bodyParserText: {},
225 | bodyParserRaw: {},
226 | bodyParserUrlencoded: {},
227 | watchOptions: {},
228 | header: {},
229 | priority: 'proxy',
230 | withFullUrlPath: false
231 | }
232 |
233 | options = { ...defaultOptions, ...options };
234 | // changeHost = true,
235 | // pathRewrite = {},
236 | // proxy: proxyConf = {},
237 | // httpProxy: httpProxyConf = {},
238 | // bodyParserConf= {},
239 | // bodyParserJSON = {},
240 | // bodyParserText = {},
241 | // bodyParserRaw = {},
242 | // bodyParserUrlencoded = {},
243 | // watchOptions = {},
244 | // header = {}
245 |
246 | if (isWatchFilePath) {
247 | // 监听配置入口文件所在的目录,一般为认为在配置文件/mock 目录下的所有文件
248 | // 加上require.resolve,保证 `./mock/`能够找到`./mock/index.js`,要不然就要监控到上一级目录了
249 | const watcher = chokidar.watch(watchFiles.map(watchFile => PATH.dirname(require.resolve(watchFile))), options.watchOptions);
250 |
251 | watcher.on('all', (event, path) => {
252 | if (event === 'change' || event === 'add') {
253 | try {
254 | // 当监听的可能是多个配置文件时,需要清理掉更新文件以及入口文件的缓存,重新获取
255 | cleanCache(path);
256 | watchFiles.forEach(file => cleanCache(file));
257 | mocker = getConfig();
258 | if (mocker._proxy) {
259 | options = { ...options, ...mocker._proxy };
260 | }
261 | console.log(`${color.green_b.black(' Done: ')} Hot Mocker ${color.green(path.replace(process.cwd(), ''))} file replacement success!`);
262 | } catch (ex) {
263 | console.error(`${color.red_b.black(' Failed: ')} Hot Mocker ${color.red(path.replace(process.cwd(), ''))} file replacement failed!!`);
264 | }
265 | }
266 | })
267 | }
268 | // 监听文件修改重新加载代码
269 | // 配置热更新
270 | app.all('/*', (req: Request, res: Response, next: NextFunction) => {
271 | const getExecUrlPath = (req: Request) => {
272 | return options.withFullUrlPath ? req.url : req.path;
273 | }
274 | /**
275 | * Get Proxy key
276 | */
277 | const proxyKey = Object.keys(options.proxy).find((kname) => {
278 | try {
279 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^' + req.method + ' ')), ''))
280 | return !!regexp.exec(getExecUrlPath(req));
281 | } catch (error) {
282 | console.error(`${color.red_b.black(' Failed: ')} The proxy configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`);
283 | return false;
284 | }
285 | });
286 | /**
287 | * Get Mocker key
288 | * => `GET /api/:owner/:repo/raw/:ref`
289 | * => `GET /api/:owner/:repo/raw/:ref/(.*)`
290 | */
291 | const mockerKey: string = Object.keys(mocker).find((kname) => {
292 | try {
293 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^' + req.method + ' ')), ''))
294 | return !!regexp.exec(getExecUrlPath(req));
295 | } catch (error) {
296 | console.error(`${color.red_b.black(' Failed: ')} The mocker configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`);
297 | return false;
298 | }
299 | });
300 | /**
301 | * Access Control Allow options.
302 | * https://github.com/jaywcjlove/mocker-api/issues/61
303 | */
304 | const accessOptions: MockerOption['header'] = {
305 | 'Access-Control-Allow-Origin': req.get('Origin') || '*',
306 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
307 | 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With,' + (req.header('access-control-request-headers') || ''),
308 | 'Access-Control-Allow-Credentials': 'true',
309 | ...options.header,
310 | }
311 | Object.keys(accessOptions).forEach(keyName => {
312 | res.setHeader(keyName, accessOptions[keyName]);
313 | });
314 | const proxyKeyString: string = Object.keys(mocker).find((kname) => {
315 | try {
316 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^(PUT|POST|GET|DELETE) ')), ''))
317 | return !!regexp.exec(getExecUrlPath(req))
318 | } catch (error) {
319 | console.error(`${color.red_b.black(' Failed: ')} The mocker configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`);
320 | return false;
321 | }
322 | })
323 | // fix issue 34 https://github.com/jaywcjlove/mocker-api/issues/34
324 | // In some cross-origin http request, the browser will send the preflighted options request before sending the request methods written in the code.
325 | if (!mockerKey && req.method.toLocaleUpperCase() === 'OPTIONS' && proxyKeyString) {
326 | return res.sendStatus(200);
327 | }
328 |
329 | /**
330 | * priority 'proxy' or 'mocker' [#151](https://github.com/jaywcjlove/mocker-api/issues/151)
331 | */
332 | if (options.priority === 'mocker') {
333 | if (mocker[mockerKey]) {
334 | return mockerHandle({ req, res, next, mocker, options, mockerKey})
335 | } else if (proxyKey && options.proxy[proxyKey]) {
336 | return proxyHandle(req, res, options, proxyKey);
337 | }
338 | } else {
339 | if (proxyKey && options.proxy[proxyKey]) {
340 | return proxyHandle(req, res, options, proxyKey);
341 | } else if (mocker[mockerKey]) {
342 | return mockerHandle({ req, res, next, mocker, options, mockerKey})
343 | }
344 | }
345 |
346 | next();
347 | });
348 |
349 | /**
350 | * The old module's resources to be released.
351 | * @param modulePath
352 | */
353 | function cleanCache(modulePath: string) {
354 | // The entry file does not have a .js suffix,
355 | // causing the module's resources not to be released.
356 | // https://github.com/jaywcjlove/webpack-api-mocker/issues/30
357 | try {
358 | modulePath = require.resolve(modulePath);
359 | } catch (e) {}
360 | var module = require.cache[modulePath];
361 | if (!module) return;
362 | // https://github.com/jaywcjlove/mocker-api/issues/42
363 | clearModule(modulePath);
364 | }
365 | /**
366 | * Merge multiple Mockers
367 | */
368 | function getConfig() {
369 | return watchFiles.reduce((mocker, file) => {
370 | const mockerItem = require(file);
371 | return Object.assign(mocker, mockerItem.default ? mockerItem.default : mockerItem);
372 | }, {})
373 | }
374 | return (req: Request, res: Response, next: NextFunction) => {
375 | next();
376 | }
377 | }
378 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 中文 ·
34 | Quick Start ·
35 | Usage ·
36 | Options ·
37 | Delayed ·
38 | Example ·
39 | License ·
40 | type
41 |
42 |
43 | `mocker-api` creates mocks for REST APIs. It is helpful when you need to test your application without the actual REST API server.
44 |
45 | **Features:**
46 |
47 | 🔥 Built-in support for hot Mocker file replacement.
48 | 🚀 Quickly and easily configure the API via JSON.
49 | 🌱 Mock API proxying made simple.
50 | 💥 Can be used independently without relying on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server).
51 |
52 | ## Quick Start
53 |
54 | ```bash
55 | mkdir mocker-app && cd mocker-app
56 |
57 | # Create a mocker configuration file based on rules
58 | touch api.js
59 |
60 | # Global install dependent.
61 | npm install mocker-api -g
62 |
63 | # Default port: 3721
64 | mocker ./api.js
65 |
66 | # Designated port
67 | # Run server at localhost:8000
68 | mocker ./api.js --host localhost --port 8000
69 | ```
70 |
71 | ## Installation
72 |
73 | you can put it the `package.json` config as a current project dependency.
74 |
75 | ```bash
76 | npm install mocker-api --save-dev
77 | ```
78 |
79 | ## Usage
80 |
81 | `mocker-api` dev support mock, configured in `mocker/index.js`.
82 |
83 | you can modify the [http-proxy](https://www.npmjs.com/package/http-proxy) options and add the event listeners by adding the httpProxy configuration
84 |
85 | ```js
86 | const proxy = {
87 | // Priority processing.
88 | // apiMocker(app, path, option)
89 | // This is the option parameter setting for apiMocker
90 | _proxy: {
91 | proxy: {
92 | // Turn a path string such as `/user/:name` into a regular expression.
93 | // https://www.npmjs.com/package/path-to-regexp
94 | '/repos/*path': 'https://api.github.com/',
95 | '/:owner/:repo/raw/:ref/*path': 'http://127.0.0.1:2018',
96 | '/api/repos/*path': 'http://127.0.0.1:3721/'
97 | },
98 | // rewrite target's url path. Object-keys will be used as RegExp to match paths.
99 | // https://github.com/jaywcjlove/mocker-api/issues/62
100 | pathRewrite: {
101 | '^/api/repos/': '/repos/',
102 | },
103 | changeHost: true,
104 | // modify the http-proxy options
105 | httpProxy: {
106 | options: {
107 | ignorePath: true,
108 | },
109 | listeners: {
110 | proxyReq: function (proxyReq, req, res, options) {
111 | console.log('proxyReq');
112 | },
113 | },
114 | },
115 | },
116 | // =====================
117 | // The default GET request.
118 | // https://github.com/jaywcjlove/mocker-api/pull/63
119 | '/api/user': {
120 | id: 1,
121 | username: 'kenny',
122 | sex: 6
123 | },
124 | 'GET /api/user': {
125 | id: 1,
126 | username: 'kenny',
127 | sex: 6
128 | },
129 | 'GET /api/user/list': [
130 | {
131 | id: 1,
132 | username: 'kenny',
133 | sex: 6
134 | }, {
135 | id: 2,
136 | username: 'kenny',
137 | sex: 6
138 | }
139 | ],
140 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => {
141 | const { owner, repo, ref } = req.params;
142 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md
143 | // owner => admin
144 | // repo => webpack-mock-api
145 | // ref => master
146 | // req.params.path => add/ddd.md
147 | return res.json({
148 | id: 1,
149 | owner, repo, ref,
150 | path: req.params.path
151 | });
152 | },
153 | 'POST /api/login/account': (req, res) => {
154 | const { password, username } = req.body;
155 | if (password === '888888' && username === 'admin') {
156 | return res.json({
157 | status: 'ok',
158 | code: 0,
159 | token: "sdfsdfsdfdsf",
160 | data: {
161 | id: 1,
162 | username: 'kenny',
163 | sex: 6
164 | }
165 | });
166 | } else {
167 | return res.status(403).json({
168 | status: 'error',
169 | code: 403
170 | });
171 | }
172 | },
173 | 'DELETE /api/user/:id': (req, res) => {
174 | console.log('---->', req.body)
175 | console.log('---->', req.params.id)
176 | res.send({ status: 'ok', message: '删除成功!' });
177 | }
178 | }
179 | module.exports = proxy;
180 | ```
181 |
182 | ## Options
183 |
184 | - [`proxy`](https://www.npmjs.com/package/path-to-regexp) => `{}` Proxy settings, Turn a path string such as `/user/:name` into a regular expression.
185 | - [`pathRewrite`](https://github.com/jaywcjlove/mocker-api/issues/62) => `{}` rewrite target's url path. Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62)
186 | - `withFullUrlPath=false` => `Boolean` the proxy regular expression support full url path. if the proxy regular expression like `/test?a=1&b=1` can be matched. [#25](https://github.com/jaywcjlove/mocker-api/issues/25)
187 | - `priority` => `proxy` priority `proxy` or `mocker` [#151](https://github.com/jaywcjlove/mocker-api/issues/151)
188 | - `changeHost` => `Boolean` Setting req headers host.
189 | - `httpProxy` => `{}` Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy)
190 | - [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser
191 | - [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser
192 | - [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser
193 | - [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser
194 | - `bodyParserConf` => `{}` bodyParser settings. eg: `bodyParserConf : {'text/plain': 'text','text/html': 'text'}` will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text`
195 | - [`watchOptions`](https://github.com/paulmillr/chokidar#api) => `object` Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api)
196 | - `header` => `{}` Access Control Allow options.
197 | ```js
198 | {
199 | header: {
200 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
201 | }
202 | }
203 | ```
204 |
205 | ⚠️ No wildcard asterisk ~~`*`~~ - use parameters instead `(.*)`, support `v1.7.3+`
206 |
207 | ⚠️ No wildcard asterisk ~~`(.*)`~~ - use parameters instead `*path`, support `v3.0.0+`
208 |
209 | ## Delayed Response
210 |
211 | You can use functional tool to enhance mock. [#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17)
212 |
213 | ```js
214 | const delay = require('mocker-api/delay');
215 | const noProxy = process.env.NO_PROXY === 'true';
216 |
217 | const proxy = {
218 | 'GET /api/user': {
219 | id: 1,
220 | username: 'kenny',
221 | sex: 6
222 | },
223 | // ...
224 | }
225 | module.exports = (noProxy ? {} : delay(proxy, 1000));
226 | ```
227 |
228 | ## apiMocker
229 |
230 | ```js
231 | apiMocker(app, mockerFilePath[, options])
232 | apiMocker(app, Mocker[, options])
233 | ```
234 |
235 | Multi entry `mocker` file watching
236 |
237 | ```js
238 | const apiMocker = require('mocker-api');
239 | const mockerFile = ['./mock/index.js'];
240 | // or
241 | // const mockerFile = './mock/index.js';
242 | apiMocker(app, mockerFile, options)
243 | ```
244 |
245 | ## Example
246 |
247 | ### Using With Command
248 |
249 | [Base example](example/base)
250 |
251 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server).
252 |
253 | ```bash
254 | # Global install dependent.
255 | npm install mocker-api -g
256 | # Run server
257 | mocker ./mocker/index.js
258 | ```
259 |
260 | Or you can put it the `package.json` config as a current project dependency.
261 |
262 | ```diff
263 | {
264 | "name": "base-example",
265 | "scripts": {
266 | + "api": "mocker ./mocker"
267 | },
268 | "devDependencies": {
269 | + "mocker-api": "2.9.5"
270 | },
271 | + "mocker": {
272 | + "port": 7788
273 | + },
274 | "license": "MIT"
275 | }
276 | ```
277 |
278 | ### Using With [Express](https://github.com/expressjs/express)
279 |
280 | [Express example](https://github.com/jaywcjlove/mocker-api/tree/master/example/express)
281 |
282 | To use api mocker on your [express](https://github.com/expressjs/express) projects.
283 |
284 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server).
285 |
286 | ```diff
287 | const express = require('express');
288 | + const path = require('path');
289 | + const apiMocker = require('mocker-api');
290 |
291 | const app = express();
292 |
293 | + apiMocker(app, path.resolve('./mocker/index.js'))
294 | app.listen(8080);
295 | ```
296 |
297 | or
298 |
299 | ```diff
300 | const express = require('express');
301 | + const apiMocker = require('mocker-api');
302 |
303 | const app = express();
304 |
305 | + apiMocker(app, {
306 | + 'GET /api/user': {
307 | + id: 1,
308 | + sex: 0
309 | + }
310 | + });
311 |
312 | app.listen(8080);
313 | ```
314 |
315 | ### Using With [Webpack](https://github.com/webpack/webpack)
316 |
317 | [webpack example](https://github.com/jaywcjlove/mocker-api/tree/master/example/webpack)
318 |
319 | To use api mocker on your [Webpack](https://github.com/webpack/webpack) projects, simply add a setup options to your [webpack-dev-server](https://github.com/webpack/webpack-dev-server) options:
320 |
321 | Change your config file to tell the dev server where to look for files: `webpack.config.js`.
322 |
323 | ```diff
324 | const HtmlWebpackPlugin = require('html-webpack-plugin');
325 | + const path = require('path');
326 | + const apiMocker = require('mocker-api');
327 |
328 | module.exports = {
329 | entry: {
330 | app: './src/index.js',
331 | },
332 | output: {
333 | filename: '[name].bundle.js',
334 | path: path.resolve(__dirname, 'dist')
335 | },
336 | + devServer: {
337 | + ...
338 | + before(app){
339 | + apiMocker(app, path.resolve('./mocker/index.js'), {
340 | + proxy: {
341 | + '/repos/*path': 'https://api.github.com/',
342 | + '/:owner/:repo/raw/:ref/*path': 'http://127.0.0.1:2018'
343 | + },
344 | + changeHost: true,
345 | + })
346 | + }
347 | + },
348 | plugins: [
349 | new HtmlWebpackPlugin({
350 | template: path.resolve('./public/index.html'),
351 | title: 'Webpack App Mocker API'
352 | })
353 | ],
354 | };
355 | ```
356 |
357 | Must have a file suffix! For example: `./mocker/index.js`.
358 |
359 | Let's add a script to easily run the dev server as well: `package.json`
360 |
361 | ```diff
362 | {
363 | "name": "development",
364 | "version": "1.0.0",
365 | "description": "",
366 | "main": "webpack.config.js",
367 | "scripts": {
368 | "test": "echo \"Error: no test specified\" && exit 1",
369 | + "start": "webpack serve --progress --mode development",
370 | "build": "webpack --mode production"
371 | },
372 | "keywords": [],
373 | "author": "",
374 | "license": "MIT",
375 | "devDependencies": {
376 | "html-webpack-plugin": "4.5.0",
377 | "mocker-api": "2.9.5",
378 | "webpack": "5.22.0",
379 | "webpack-cli": "4.5.0",
380 | "webpack-dev-server": "3.11.2"
381 | }
382 | }
383 | ```
384 |
385 | Mock API proxying made simple.
386 |
387 | ```diff
388 | {
389 | before(app){
390 | + apiMocker(app, path.resolve('./mocker/index.js'), {
391 | + proxy: {
392 | + '/repos/*path': 'https://api.github.com/',
393 | + },
394 | + changeHost: true,
395 | + })
396 | }
397 | }
398 | ```
399 |
400 | ### Using With create-react-app
401 |
402 | [create-react-app example](https://github.com/jaywcjlove/mocker-api/tree/master/example/create-react-app)
403 |
404 | To use api mocker on your [create-react-app](https://github.com/facebook/create-react-app/blob/3f699fd08044de9ab0ce1991a66b376d3e1956a8/docusaurus/docs/proxying-api-requests-in-development.md) projects. create [`src/setupProxy.js`](https://github.com/jaywcjlove/mocker-api/blob/64a093685b05c70ab0ddcf3fd5dbede7871efa8a/example/create-react-app/src/setupProxy.js#L1-L11) and place the following contents in it:
405 |
406 | ```diff
407 | + const apiMocker = require('mocker-api');
408 | + const path = require('path');
409 |
410 | module.exports = function(app) {
411 | + apiMocker(app, path.resolve('./mocker/index.js'), {
412 | + proxy: {
413 | + '/repos/*path': 'https://api.github.com/',
414 | + },
415 | + changeHost: true,
416 | + });
417 | };
418 | ```
419 |
420 | ```diff
421 | {
422 | .....
423 | "devDependencies": {
424 | + "mocker-api": "2.9.5"
425 | },
426 | ....
427 | }
428 | ```
429 |
430 | ### Development
431 |
432 | ```shell
433 | $ yarn install
434 | $ yarn run build
435 | $ yarn run watch
436 | $ yarn run test
437 | ```
438 |
439 | ## Contributors
440 |
441 | As always, thanks to our amazing contributors!
442 |
443 |
444 |
445 |
446 |
447 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors).
448 |
449 | ## License
450 |
451 | [MIT © Kenny Wong](https://github.com/jaywcjlove/mocker-api/blob/master/LICENSE)
452 |
--------------------------------------------------------------------------------