@kurly-growth/growthman 0.1.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.
Files changed (44) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/CLAUDE.md +58 -0
  3. package/README.md +23 -0
  4. package/app/api/endpoints/[id]/route.ts +63 -0
  5. package/app/api/endpoints/bulk/route.ts +23 -0
  6. package/app/api/endpoints/import/route.ts +52 -0
  7. package/app/api/endpoints/route.ts +39 -0
  8. package/app/api/endpoints/sync/route.ts +80 -0
  9. package/app/api/mock/[...slug]/route.ts +82 -0
  10. package/app/favicon.ico +0 -0
  11. package/app/globals.css +125 -0
  12. package/app/layout.tsx +36 -0
  13. package/app/page.tsx +184 -0
  14. package/bin/cli.js +28 -0
  15. package/components/endpoint-edit-dialog.tsx +181 -0
  16. package/components/endpoint-table.tsx +133 -0
  17. package/components/openapi-upload-dialog.tsx +196 -0
  18. package/components/ui/button.tsx +62 -0
  19. package/components/ui/checkbox.tsx +32 -0
  20. package/components/ui/dialog.tsx +143 -0
  21. package/components/ui/input.tsx +21 -0
  22. package/components/ui/label.tsx +24 -0
  23. package/components/ui/sonner.tsx +37 -0
  24. package/components/ui/table.tsx +116 -0
  25. package/components/ui/textarea.tsx +18 -0
  26. package/components.json +22 -0
  27. package/eslint.config.mjs +18 -0
  28. package/lib/openapi-parser.ts +270 -0
  29. package/lib/prisma.ts +9 -0
  30. package/lib/utils.ts +6 -0
  31. package/lib/validation.ts +19 -0
  32. package/next.config.ts +7 -0
  33. package/package.json +56 -0
  34. package/pnpm-workspace.yaml +4 -0
  35. package/postcss.config.mjs +7 -0
  36. package/prisma/schema.prisma +23 -0
  37. package/prisma/seed.ts +29 -0
  38. package/public/file.svg +1 -0
  39. package/public/globe.svg +1 -0
  40. package/public/next.svg +1 -0
  41. package/public/vercel.svg +1 -0
  42. package/public/window.svg +1 -0
  43. package/tsconfig.json +34 -0
  44. package/types/endpoint.ts +10 -0
@@ -0,0 +1,270 @@
1
+ interface OpenAPISpec {
2
+ paths?: Record<string, Record<string, OpenAPIOperation>>
3
+ components?: {
4
+ schemas?: Record<string, OpenAPISchema>
5
+ }
6
+ }
7
+
8
+ interface OpenAPIOperation {
9
+ summary?: string
10
+ description?: string
11
+ responses?: Record<string, OpenAPIResponse>
12
+ requestBody?: {
13
+ content?: Record<string, { schema?: OpenAPISchema; example?: unknown }>
14
+ }
15
+ }
16
+
17
+ interface OpenAPIResponse {
18
+ description?: string
19
+ content?: Record<string, { schema?: OpenAPISchema; example?: unknown }>
20
+ }
21
+
22
+ interface OpenAPISchema {
23
+ type?: string
24
+ format?: string
25
+ properties?: Record<string, OpenAPISchema>
26
+ items?: OpenAPISchema
27
+ example?: unknown
28
+ default?: unknown
29
+ enum?: unknown[]
30
+ $ref?: string
31
+ allOf?: OpenAPISchema[]
32
+ oneOf?: OpenAPISchema[]
33
+ anyOf?: OpenAPISchema[]
34
+ nullable?: boolean
35
+ required?: string[]
36
+ additionalProperties?: boolean | OpenAPISchema
37
+ }
38
+
39
+ export interface ParsedEndpoint {
40
+ path: string
41
+ method: string
42
+ description: string | null
43
+ statusCode: number
44
+ responseBody: unknown
45
+ }
46
+
47
+ function resolveRef(ref: string, spec: OpenAPISpec): OpenAPISchema | null {
48
+ // $ref format: "#/components/schemas/SchemaName"
49
+ const parts = ref.split('/')
50
+ if (parts[0] !== '#' || parts[1] !== 'components' || parts[2] !== 'schemas') {
51
+ return null
52
+ }
53
+ const schemaName = parts[3]
54
+ return spec.components?.schemas?.[schemaName] || null
55
+ }
56
+
57
+ function generateMockValue(
58
+ schema: OpenAPISchema,
59
+ spec: OpenAPISpec,
60
+ visited: Set<string> = new Set()
61
+ ): unknown {
62
+ // Handle $ref
63
+ if (schema.$ref) {
64
+ // Prevent circular references
65
+ if (visited.has(schema.$ref)) {
66
+ return {}
67
+ }
68
+ visited.add(schema.$ref)
69
+ const resolved = resolveRef(schema.$ref, spec)
70
+ if (resolved) {
71
+ return generateMockValue(resolved, spec, visited)
72
+ }
73
+ return {}
74
+ }
75
+
76
+ // Handle allOf (merge schemas)
77
+ if (schema.allOf && schema.allOf.length > 0) {
78
+ const merged: Record<string, unknown> = {}
79
+ for (const subSchema of schema.allOf) {
80
+ const value = generateMockValue(subSchema, spec, visited)
81
+ if (typeof value === 'object' && value !== null) {
82
+ Object.assign(merged, value)
83
+ }
84
+ }
85
+ return merged
86
+ }
87
+
88
+ // Handle oneOf/anyOf (use first option)
89
+ if (schema.oneOf && schema.oneOf.length > 0) {
90
+ return generateMockValue(schema.oneOf[0], spec, visited)
91
+ }
92
+ if (schema.anyOf && schema.anyOf.length > 0) {
93
+ return generateMockValue(schema.anyOf[0], spec, visited)
94
+ }
95
+
96
+ // Use example or default if available
97
+ if (schema.example !== undefined) return schema.example
98
+ if (schema.default !== undefined) return schema.default
99
+ if (schema.enum && schema.enum.length > 0) return schema.enum[0]
100
+
101
+ // Generate based on type and format
102
+ switch (schema.type) {
103
+ case 'string':
104
+ return generateStringValue(schema.format)
105
+ case 'number':
106
+ return generateNumberValue(schema.format)
107
+ case 'integer':
108
+ return generateIntegerValue(schema.format)
109
+ case 'boolean':
110
+ return true
111
+ case 'array':
112
+ if (schema.items) {
113
+ return [generateMockValue(schema.items, spec, visited)]
114
+ }
115
+ return []
116
+ case 'object':
117
+ return generateObjectValue(schema, spec, visited)
118
+ default:
119
+ // If no type but has properties, treat as object
120
+ if (schema.properties) {
121
+ return generateObjectValue(schema, spec, visited)
122
+ }
123
+ return null
124
+ }
125
+ }
126
+
127
+ function generateStringValue(format?: string): string {
128
+ switch (format) {
129
+ case 'date':
130
+ return '2024-01-15'
131
+ case 'date-time':
132
+ return '2024-01-15T09:30:00Z'
133
+ case 'time':
134
+ return '09:30:00'
135
+ case 'email':
136
+ return 'user@example.com'
137
+ case 'uri':
138
+ case 'url':
139
+ return 'https://example.com'
140
+ case 'uuid':
141
+ return '550e8400-e29b-41d4-a716-446655440000'
142
+ case 'hostname':
143
+ return 'example.com'
144
+ case 'ipv4':
145
+ return '192.168.1.1'
146
+ case 'ipv6':
147
+ return '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
148
+ case 'byte':
149
+ return 'SGVsbG8gV29ybGQ='
150
+ case 'binary':
151
+ return '<binary>'
152
+ case 'password':
153
+ return '********'
154
+ case 'phone':
155
+ return '+1-555-555-5555'
156
+ default:
157
+ return 'string'
158
+ }
159
+ }
160
+
161
+ function generateNumberValue(format?: string): number {
162
+ switch (format) {
163
+ case 'float':
164
+ return 1.5
165
+ case 'double':
166
+ return 1.23456789
167
+ default:
168
+ return 0
169
+ }
170
+ }
171
+
172
+ function generateIntegerValue(format?: string): number {
173
+ switch (format) {
174
+ case 'int32':
175
+ return 123
176
+ case 'int64':
177
+ return 1234567890
178
+ default:
179
+ return 0
180
+ }
181
+ }
182
+
183
+ function generateObjectValue(
184
+ schema: OpenAPISchema,
185
+ spec: OpenAPISpec,
186
+ visited: Set<string>
187
+ ): Record<string, unknown> {
188
+ const obj: Record<string, unknown> = {}
189
+
190
+ if (schema.properties) {
191
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
192
+ obj[key] = generateMockValue(propSchema, spec, visited)
193
+ }
194
+ }
195
+
196
+ return obj
197
+ }
198
+
199
+ export function parseOpenAPISpec(spec: OpenAPISpec): ParsedEndpoint[] {
200
+ const endpoints: ParsedEndpoint[] = []
201
+
202
+ if (!spec.paths) return endpoints
203
+
204
+ for (const [path, methods] of Object.entries(spec.paths)) {
205
+ for (const [method, operation] of Object.entries(methods)) {
206
+ // Skip non-HTTP method properties like 'parameters'
207
+ if (
208
+ !['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(
209
+ method.toLowerCase()
210
+ )
211
+ ) {
212
+ continue
213
+ }
214
+
215
+ const description = operation.summary || operation.description || null
216
+
217
+ // Find successful response (2xx)
218
+ let statusCode = 200
219
+ let responseBody: unknown = {}
220
+
221
+ if (operation.responses) {
222
+ // Sort response codes to prefer 200, then other 2xx
223
+ const responseCodes = Object.keys(operation.responses).sort((a, b) => {
224
+ if (a === '200') return -1
225
+ if (b === '200') return 1
226
+ return parseInt(a, 10) - parseInt(b, 10)
227
+ })
228
+
229
+ for (const code of responseCodes) {
230
+ const codeNum = parseInt(code, 10)
231
+ if (codeNum >= 200 && codeNum < 300) {
232
+ statusCode = codeNum
233
+ const response = operation.responses[code]
234
+
235
+ if (response.content) {
236
+ // Try application/json first, then any JSON-like content type
237
+ const jsonContent =
238
+ response.content['application/json'] ||
239
+ response.content['application/hal+json'] ||
240
+ response.content['application/vnd.api+json'] ||
241
+ Object.values(response.content).find((c) => c.schema)
242
+
243
+ if (jsonContent) {
244
+ if (jsonContent.example) {
245
+ responseBody = jsonContent.example
246
+ } else if (jsonContent.schema) {
247
+ responseBody = generateMockValue(jsonContent.schema, spec)
248
+ }
249
+ }
250
+ }
251
+ break
252
+ }
253
+ }
254
+ }
255
+
256
+ // Convert OpenAPI path params {id} to Express-style :id
257
+ const normalizedPath = path.replace(/\{(\w+)\}/g, ':$1')
258
+
259
+ endpoints.push({
260
+ path: normalizedPath,
261
+ method: method.toUpperCase(),
262
+ description,
263
+ statusCode,
264
+ responseBody,
265
+ })
266
+ }
267
+ }
268
+
269
+ return endpoints
270
+ }
package/lib/prisma.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { PrismaClient } from '@prisma/client'
2
+
3
+ const globalForPrisma = globalThis as unknown as {
4
+ prisma: PrismaClient | undefined
5
+ }
6
+
7
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient()
8
+
9
+ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,19 @@
1
+ const VALID_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
2
+
3
+ export function validatePath(path: unknown): path is string {
4
+ if (!path || typeof path !== 'string') return false
5
+ if (!path.startsWith('/')) return false
6
+ if (path.length > 1000) return false
7
+ return true
8
+ }
9
+
10
+ export function validateMethod(method: unknown): method is string {
11
+ if (typeof method !== 'string') return false
12
+ return VALID_METHODS.includes(method.toUpperCase())
13
+ }
14
+
15
+ export function validateStatusCode(statusCode: unknown): statusCode is number {
16
+ if (statusCode === undefined || statusCode === null) return true // optional
17
+ if (typeof statusCode !== 'number') return false
18
+ return statusCode >= 100 && statusCode < 600
19
+ }
package/next.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@kurly-growth/growthman",
3
+ "version": "0.1.0",
4
+ "description": "Local mock API server with UI dashboard",
5
+ "bin": {
6
+ "growthman": "./bin/cli.js"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "scripts": {
12
+ "prepack": "next build",
13
+ "postinstall": "prisma generate && prisma db push",
14
+ "dev": "next dev",
15
+ "dev:clean": "npx prisma db seed && next dev",
16
+ "build": "next build",
17
+ "start": "next start",
18
+ "lint": "eslint",
19
+ "db:seed": "npx prisma db seed",
20
+ "db:reset": "rm -f prisma/dev.db && npx prisma db push && npx prisma db seed"
21
+ },
22
+ "prisma": {
23
+ "seed": "npx tsx prisma/seed.ts"
24
+ },
25
+ "dependencies": {
26
+ "@monaco-editor/react": "^4.7.0",
27
+ "@prisma/client": "^6.19.1",
28
+ "@radix-ui/react-checkbox": "^1.3.3",
29
+ "@radix-ui/react-dialog": "^1.1.15",
30
+ "@radix-ui/react-label": "^2.1.8",
31
+ "@radix-ui/react-slot": "^1.2.4",
32
+ "class-variance-authority": "^0.7.1",
33
+ "clsx": "^2.1.1",
34
+ "lucide-react": "^0.562.0",
35
+ "next": "16.1.1",
36
+ "next-themes": "^0.4.6",
37
+ "path-to-regexp": "^8.3.0",
38
+ "prisma": "^6.19.1",
39
+ "react": "19.2.3",
40
+ "react-dom": "19.2.3",
41
+ "sonner": "^2.0.7",
42
+ "tailwind-merge": "^3.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@tailwindcss/postcss": "^4",
46
+ "@types/node": "^20",
47
+ "@types/react": "^19",
48
+ "@types/react-dom": "^19",
49
+ "eslint": "^9",
50
+ "eslint-config-next": "16.1.1",
51
+ "tailwindcss": "^4",
52
+ "tsx": "^4.21.0",
53
+ "tw-animate-css": "^1.4.0",
54
+ "typescript": "^5"
55
+ }
56
+ }
@@ -0,0 +1,4 @@
1
+ onlyBuiltDependencies:
2
+ - "@prisma/client"
3
+ - "@prisma/engines"
4
+ - "prisma"
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,23 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "sqlite"
7
+ url = "file:./dev.db"
8
+ }
9
+
10
+ model MockEndpoint {
11
+ id String @id @default(cuid())
12
+ path String // 예: /v1/missions
13
+ method String // GET, POST, PUT, DELETE 등
14
+ description String? // API 설명
15
+ statusCode Int @default(200) // 응답할 상태 코드
16
+ responseBody Json // 내려줄 가짜 데이터
17
+
18
+ createdAt DateTime @default(now())
19
+ updatedAt DateTime @updatedAt
20
+
21
+ @@unique([path, method]) // path와 method 조합이 유일해야 함
22
+ @@map("mock_endpoints") // 테이블 이름을 snake_case로 매핑
23
+ }
package/prisma/seed.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { PrismaClient } from '@prisma/client'
2
+
3
+ const prisma = new PrismaClient()
4
+
5
+ async function main() {
6
+ // 기존 데이터 삭제
7
+ await prisma.mockEndpoint.deleteMany()
8
+ console.log('Cleared all mock endpoints')
9
+
10
+ // 샘플 데이터 추가 (선택사항)
11
+ // await prisma.mockEndpoint.create({
12
+ // data: {
13
+ // path: '/v1/health',
14
+ // method: 'GET',
15
+ // description: 'Health check',
16
+ // statusCode: 200,
17
+ // responseBody: { status: 'ok' },
18
+ // },
19
+ // })
20
+ }
21
+
22
+ main()
23
+ .catch((e) => {
24
+ console.error(e)
25
+ process.exit(1)
26
+ })
27
+ .finally(async () => {
28
+ await prisma.$disconnect()
29
+ })
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -0,0 +1,10 @@
1
+ export interface MockEndpoint {
2
+ id: string
3
+ path: string
4
+ method: string
5
+ description: string | null
6
+ statusCode: number
7
+ responseBody: unknown
8
+ createdAt: string
9
+ updatedAt: string
10
+ }