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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

44 changes: 0 additions & 44 deletions .eslintrc.json

This file was deleted.

10 changes: 6 additions & 4 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# This workflow will do a clean install of node dependencies, lint the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI
Expand All @@ -16,14 +16,16 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x, 20.x, 22.x]
node-version: [20.x, 22.x, 24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
79 changes: 79 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# AGENT.md

## Project Overview

**servemocks** is a mock server tool that generates REST APIs from JSON, XML, and other sample files in a directory. It's built on Express and can be used as a CLI tool or mounted as middleware in an existing Express app.

## Commands

```bash
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Lint
npm run lint

# Fix lint issues
npm run lint:fix

# Release (uses standard-version)
npm run release # patch release
npm run release:patch # patch release
npm run release:minor # minor release
npm run release:major # major release
```

## Quality checks

- Make sure to run `npm test` and `npm run lint:fix` after changes have been finished
- Keep the project lightweight and avoid adding more dependencies
- Make sure to update `src/serve-mocks.spec.js` with tests for new features, it is basically a kind of e2e test which covers most of logic

## Project Structure

```
src/
├── serve-mocks.js # Main entry point
├── public-api.js # Public exports
├── mock-file-types.js # Supported file types
├── cli.js # CLI implementation
├── serve-mocks.spec.js # Main tests
├── service/
│ ├── endpoint-registration.service.js # Endpoint registration logic
│ ├── script-evaluation.service.js # Dynamic response (.mjs) evaluation
│ └── logger.js # Logging utility
└── utilities/
├── sorting.js # Endpoint sorting by specificity
├── http-method.js # HTTP method utilities
├── encoding.js # Encoding utilities
└── async-utilities.js # Async utilities
```

## Key Concepts

### Mock File Conventions
- File naming: `<http-method>.<extension>` or `<path>.<http-method>.<extension>`
- Example: `user.post.json` → POST /user
- `[any]` can be used as a wildcard for any path segment
- HTTP method is derived from the filename (get, post, put, patch, delete)

### Dynamic Responses
- Use `.mjs` files for dynamic/mock responses
- Supported modes: `eval`, `dynamicImport`, `disabled`

### Options
- `responseDelay_ms`: Default delay before responses (default: 100)
- `dynamicMockResponsesMode`: `'eval' | 'dynamicImport' | 'disabled'` (default: `'eval'`)
- `endpointRegistrationLogging`: `'verbose' | 'compact' | 'disabled'` (default: `'verbose'`)

## Dependencies

- **express**: Web framework
- **chalk**: Terminal colors
- **commander**: CLI parsing
- **cors**: CORS middleware
- **glob**: File pattern matching
- **ajv**: JSON Schema validation
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,36 @@ Starting from version 2 it is also possible to produce dynamic mock responses by
as mock file. Use the file extension `.mjs` to enable this function.
Example: [examples/v3](https://github.com/diva-e/servemocks/tree/main/examples/mock-api/v3).

## OpenAPI Import

You can import endpoints from an OpenAPI 3.x specification to generate mock files:

```bash
servemocks import <spec-file> <output-dir> [-i, --ignore-examples] [--on-conflict <strategy>]
```

**Options:**

`-i, --ignore-examples` - Ignore examples in the spec and generate your own

`--on-conflict <strategy>` - What to do with mock files that already exist: `fail`, `skip`, `overwrite` or `prompt`.
Defaults to `prompt` in an interactive terminal and to `fail` otherwise (CI, coding agents), so the import never waits for input.
With `fail`, nothing is written if any file already exists.

**Example:**

```bash
servemocks import openapi.json ./mock-api
```

This will create mock files for each endpoint in the specification:

* File naming uses explicit HTTP method (e.g., `user.get.json`, `user.post.json`)
* Path parameters are converted to wildcards (e.g., `/pets/{petId}` → `pets/[any].get.json`)
* GET mock files contain the response body only, other methods use the `requestOptions`/`responseOptions`/`response` format
* Request body schemas are converted to JSON Schema validation
* Response examples from the spec are used when available, otherwise a deterministic example is generated from the schema

## Getting Started

### Install
Expand All @@ -54,10 +84,18 @@ npm add -D servemocks

### Usage

**Start the mock server:**

```plain
servemocks <directory> [-p, --port=8080] [-c, --compact-logging]
```

**Import from OpenAPI spec:**

```plain
servemocks import <spec-file> <output-dir> [-i, --ignore-examples] [--on-conflict <strategy>]
```

Example:

```bash
Expand Down
37 changes: 35 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
#!/usr/bin/env node

import { program } from 'commander'
import { program, Option } from 'commander'
import { serveMocks } from './src/serve-mocks.js'
import { importOpenAPI, CONFLICT_STRATEGIES } from './src/service/openapi-import.service.js'
import { readFileSync } from 'fs'
import { Logger } from './src/service/logger.js'

import { fileURLToPath } from 'url'
import path from 'path'

const logger = new Logger()

const packageJsonPath = path.join(fileURLToPath(import.meta.url), '../package.json')
const { version } = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf-8' }))

const hostname = process.env.SERVEMOCKS_HOST || '127.0.0.1'

program
.name('servemocks')
.description('Conventional Mock-Server')
.version(version)

program
.command('import <spec-file> <output-dir>')
.description('Import endpoints from an OpenAPI 3.x specification')
.option('-i, --ignore-examples', 'Ignore examples in the spec and generate our own')
.addOption(
new Option(
'--on-conflict <strategy>',
'How to handle existing mock files (default: prompt in an interactive terminal, otherwise fail)'
)
.choices(CONFLICT_STRATEGIES)
)
.action(async (specFile, outputDir, options) => {
try {
await importOpenAPI(specFile, outputDir, {
ignoreExamples: options.ignoreExamples,
// never wait for user input when there is nobody to answer (CI, coding agents)
onConflict: options.onConflict || (process.stdin.isTTY ? 'prompt' : 'fail')
})
} catch (error) {
logger.error('Import failed:', error.message)
process.exit(1)
}
})

program
.arguments('<mock-directory>')
.option('-p, --port <port>', 'Change webserver port')
.option('-c, --compact-logging', 'Limits the number of endpoints which are being printed to the console on init')
Expand All @@ -24,4 +56,5 @@ program
}
serveMocks(mockDirectory, env.port || 8080, hostname, options)
})
.parse(process.argv)

program.parse(process.argv)
53 changes: 53 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import js from '@eslint/js'
import jsdoc from 'eslint-plugin-jsdoc'
import globals from 'globals'

export default [
{
ignores: ['examples/**', 'node_modules/**']
},
js.configs.recommended,
{
files: ['**/*.js'],
plugins: {
jsdoc
},
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
globals: {
...globals.node,
...globals.jest
}
},
rules: {
'indent': ['error', 2],
'linebreak-style': ['error', 'unix'],
'quotes': ['error', 'single'],
'max-len': ['warn', 120],
'semi': ['warn', 'never'],
'comma-dangle': ['error', 'only-multiline'],
'no-unused-vars': ['error', {
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}],
'no-console': 'error',
'jsdoc/require-param-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-jsdoc': ['error', {
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true
}
}]
}
},
{
files: ['**/*.spec.js'],
rules: {
'no-console': 'off'
}
}
]
4 changes: 4 additions & 0 deletions examples/mock-api/v1/[any]/company/[any].json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "4711",
"employeeName": "Erika Mustermann"
}
Loading
Loading