mockaton 12.3.7 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/server/ProxyRelay.js +7 -2
- package/src/server/cacheBustResolver.js +6 -3
- package/src/server/config.js +2 -2
- package/src/server/utils/HttpIncomingMessage.js +2 -2
- package/src/server/utils/UrlParsers.js +29 -0
- package/src/server/utils/UrlParsers.test.js +23 -0
- package/src/server/utils/fs.js +5 -11
- package/src/server/utils/openInBrowser.js +12 -3
package/index.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export const jsToJsonPlugin: Plugin
|
|
|
61
61
|
export function jwtCookie(cookieName: string, payload: any, path?: string): string
|
|
62
62
|
|
|
63
63
|
export function parseJSON(request: IncomingMessage): Promise<any>
|
|
64
|
+
export function parseSplats(reqUrl: string, filename: string): Record<string, string>
|
|
65
|
+
export function parseQueryParams(reqUrl: string): URLSearchParams
|
|
64
66
|
|
|
65
67
|
export type JsonPromise<T> = Promise<Response & { json(): Promise<T> }>
|
|
66
68
|
|
package/index.js
CHANGED
|
@@ -4,5 +4,6 @@ export { Mockaton } from './src/server/Mockaton.js'
|
|
|
4
4
|
export { jwtCookie } from './src/server/utils/jwt.js'
|
|
5
5
|
export { jsToJsonPlugin } from './src/server/MockDispatcher.js'
|
|
6
6
|
export { parseJSON, BodyReaderError } from './src/server/utils/HttpIncomingMessage.js'
|
|
7
|
+
export { parseSplats, parseQueryParams } from './src/server/utils/UrlParsers.js'
|
|
7
8
|
|
|
8
9
|
export const defineConfig = opts => opts
|
package/package.json
CHANGED
package/src/server/ProxyRelay.js
CHANGED
|
@@ -6,7 +6,7 @@ import { write, isFile } from './utils/fs.js'
|
|
|
6
6
|
import { readBody, BodyReaderError } from './utils/HttpIncomingMessage.js'
|
|
7
7
|
|
|
8
8
|
import { config } from './config.js'
|
|
9
|
-
|
|
9
|
+
import { logger } from './utils/logger.js'
|
|
10
10
|
import { makeMockFilename } from '../client/Filename.js'
|
|
11
11
|
|
|
12
12
|
|
|
@@ -47,6 +47,11 @@ export async function proxy(req, response, delay) {
|
|
|
47
47
|
data = JSON.stringify(JSON.parse(body), null, ' ')
|
|
48
48
|
}
|
|
49
49
|
catch {}
|
|
50
|
-
|
|
50
|
+
try {
|
|
51
|
+
write(join(config.mocksDir, filename), data)
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.warn('Write access denied', err)
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { resolve as r } from 'node:path'
|
|
2
|
+
|
|
3
|
+
const mockatonSrcRoot = `file://${r(import.meta.dirname, '..')}`
|
|
4
|
+
|
|
1
5
|
// We register this hook at runtime so it doesn’t interfere with non-dynamic imports.
|
|
6
|
+
// Excluding src/ is only needed for DEV.
|
|
2
7
|
export async function resolve(specifier, context, nextResolve) {
|
|
3
8
|
const result = await nextResolve(specifier, context)
|
|
4
|
-
if (
|
|
5
|
-
return result
|
|
6
|
-
if (result.url?.startsWith('file:')) {
|
|
9
|
+
if (result.url?.startsWith('file://') && !result.url.startsWith(mockatonSrcRoot)) {
|
|
7
10
|
const url = new URL(result.url)
|
|
8
11
|
url.searchParams.set('t', performance.now())
|
|
9
12
|
return {
|
package/src/server/config.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
+
import { METHODS } from 'node:http'
|
|
2
3
|
|
|
3
4
|
import { logger } from './utils/logger.js'
|
|
4
5
|
import { isDirectory } from './utils/fs.js'
|
|
5
6
|
import { openInBrowser } from './utils/openInBrowser.js'
|
|
6
7
|
import { optional, is, validate } from './utils/validate.js'
|
|
7
|
-
import { SUPPORTED_METHODS } from './utils/HttpIncomingMessage.js'
|
|
8
8
|
import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/http-cors.js'
|
|
9
9
|
|
|
10
10
|
import { jsToJsonPlugin } from './MockDispatcher.js'
|
|
@@ -41,7 +41,7 @@ const schema = {
|
|
|
41
41
|
|
|
42
42
|
corsAllowed: [true, is(Boolean)],
|
|
43
43
|
corsOrigins: [['*'], validateCorsAllowedOrigins],
|
|
44
|
-
corsMethods: [
|
|
44
|
+
corsMethods: [METHODS, validateCorsAllowedMethods],
|
|
45
45
|
corsHeaders: [['content-type', 'authorization'], Array.isArray],
|
|
46
46
|
corsExposedHeaders: [[], Array.isArray],
|
|
47
47
|
corsCredentials: [true, is(Boolean)],
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import http, { METHODS } from 'node:http'
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
export const methodIsSupported = method => SUPPORTED_METHODS.includes(method)
|
|
4
|
+
export const methodIsSupported = method => METHODS.includes(method)
|
|
6
5
|
|
|
7
6
|
export class BodyReaderError extends Error {
|
|
8
7
|
name = 'BodyReaderError'
|
|
@@ -73,3 +72,4 @@ export function decode(url) {
|
|
|
73
72
|
? candidate
|
|
74
73
|
: '' // reject multiple encodings
|
|
75
74
|
}
|
|
75
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { relative } from 'node:path'
|
|
2
|
+
import { config } from '../config.js'
|
|
3
|
+
import { parseFilename } from '../../client/Filename.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function parseQueryParams(url) {
|
|
7
|
+
return new URL(url, 'http://_').searchParams
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseSplats(url, filename) {
|
|
11
|
+
const { urlMask } = parseFilename(relative(config.mocksDir, filename))
|
|
12
|
+
|
|
13
|
+
const splats = []
|
|
14
|
+
const pattern = urlMask
|
|
15
|
+
.replace(/\[(.+?)]/g, (_, name) => {
|
|
16
|
+
splats.push(name)
|
|
17
|
+
return '([^/]+)'
|
|
18
|
+
})
|
|
19
|
+
.replace(/\//g, '\\/')
|
|
20
|
+
|
|
21
|
+
const match = url.match(new RegExp(`^${pattern}$`))
|
|
22
|
+
if (!match)
|
|
23
|
+
return {}
|
|
24
|
+
|
|
25
|
+
return splats.reduce((acc, name, i) => {
|
|
26
|
+
acc[name] = match[i + 1]
|
|
27
|
+
return acc
|
|
28
|
+
}, {})
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { deepEqual, equal } from 'node:assert/strict'
|
|
3
|
+
import { parseSplats, parseQueryParams } from './UrlParsers.js'
|
|
4
|
+
import { config } from '../config.js'
|
|
5
|
+
|
|
6
|
+
test('parseQueryParams', () => {
|
|
7
|
+
const searchParams = parseQueryParams('/api/foo?limit=123')
|
|
8
|
+
equal(searchParams.get('limit'), '123')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
test('parseSplats', () => {
|
|
13
|
+
const p = parseSplats(
|
|
14
|
+
'/api/company/123/user/456',
|
|
15
|
+
`${config.mocksDir}/api/company/[companyId]/user/[userId](comments).GET.200.js`
|
|
16
|
+
)
|
|
17
|
+
deepEqual(p, {
|
|
18
|
+
companyId: '123',
|
|
19
|
+
userId: '456',
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
|
package/src/server/utils/fs.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { join, dirname, sep, posix } from 'node:path'
|
|
2
2
|
import { lstatSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
3
3
|
|
|
4
|
-
import { logger } from './logger.js'
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
export const isFile = path => lstatSync(path, { throwIfNoEntry: false })?.isFile()
|
|
8
6
|
export const isDirectory = path => lstatSync(path, { throwIfNoEntry: false })?.isDirectory()
|
|
9
7
|
|
|
8
|
+
|
|
10
9
|
/** @returns {Array<string>} paths relative to `dir` */
|
|
11
|
-
export
|
|
10
|
+
export function listFilesRecursively(dir) {
|
|
12
11
|
try {
|
|
13
12
|
const files = readdirSync(dir, { recursive: true }).filter(f => isFile(join(dir, f)))
|
|
14
13
|
return process.platform === 'win32'
|
|
@@ -20,12 +19,7 @@ export const listFilesRecursively = dir => {
|
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
writeFileSync(path, body)
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
logger.warn('Write access denied', err)
|
|
30
|
-
}
|
|
22
|
+
export function write(path, body) {
|
|
23
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
24
|
+
writeFileSync(path, body)
|
|
31
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export const openInBrowser = (async () => {
|
|
@@ -11,13 +11,22 @@ export const openInBrowser = (async () => {
|
|
|
11
11
|
})()
|
|
12
12
|
|
|
13
13
|
function _openInBrowser(address) {
|
|
14
|
+
let opener
|
|
14
15
|
switch (process.platform) {
|
|
15
16
|
case 'darwin':
|
|
16
|
-
|
|
17
|
+
opener = 'open'
|
|
17
18
|
break
|
|
18
19
|
case 'win32':
|
|
19
|
-
|
|
20
|
+
opener = 'start'
|
|
20
21
|
break
|
|
22
|
+
default:
|
|
23
|
+
opener = ['xdg-open', 'gnome-open', 'kde-open'].find(hasCommand)
|
|
21
24
|
}
|
|
25
|
+
if (opener)
|
|
26
|
+
spawnSync(opener, [address])
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
function hasCommand(cmd) {
|
|
30
|
+
const { status } = spawnSync('command', ['-v', cmd], { stdio: 'ignore' })
|
|
31
|
+
return status === 0
|
|
32
|
+
}
|