@kurly-growth/growthman 0.1.14 → 0.1.15

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.
@@ -0,0 +1,63 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { prisma } from '@/lib/prisma'
3
+ import { validatePath, validateMethod, validateStatusCode } from '@/lib/validation'
4
+
5
+ type RouteParams = {
6
+ params: Promise<{ id: string }>
7
+ }
8
+
9
+ // GET: 단일 엔드포인트 조회
10
+ export async function GET(_request: NextRequest, { params }: RouteParams) {
11
+ const { id } = await params
12
+
13
+ const endpoint = await prisma.mockEndpoint.findUnique({
14
+ where: { id },
15
+ })
16
+
17
+ if (!endpoint) {
18
+ return NextResponse.json({ error: 'Endpoint not found' }, { status: 404 })
19
+ }
20
+
21
+ return NextResponse.json(endpoint)
22
+ }
23
+
24
+ // PUT: 엔드포인트 수정
25
+ export async function PUT(request: NextRequest, { params }: RouteParams) {
26
+ const { id } = await params
27
+ const body = await request.json()
28
+ const { path, method, description, statusCode, responseBody } = body
29
+
30
+ if (path !== undefined && !validatePath(path)) {
31
+ return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
32
+ }
33
+ if (method !== undefined && !validateMethod(method)) {
34
+ return NextResponse.json({ error: 'Invalid method' }, { status: 400 })
35
+ }
36
+ if (!validateStatusCode(statusCode)) {
37
+ return NextResponse.json({ error: 'Invalid status code' }, { status: 400 })
38
+ }
39
+
40
+ const endpoint = await prisma.mockEndpoint.update({
41
+ where: { id },
42
+ data: {
43
+ path,
44
+ method: method?.toUpperCase(),
45
+ description,
46
+ statusCode,
47
+ responseBody,
48
+ },
49
+ })
50
+
51
+ return NextResponse.json(endpoint)
52
+ }
53
+
54
+ // DELETE: 엔드포인트 삭제
55
+ export async function DELETE(_request: NextRequest, { params }: RouteParams) {
56
+ const { id } = await params
57
+
58
+ await prisma.mockEndpoint.delete({
59
+ where: { id },
60
+ })
61
+
62
+ return NextResponse.json({ success: true })
63
+ }
@@ -0,0 +1,23 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { prisma } from '@/lib/prisma'
3
+
4
+ // DELETE: 여러 엔드포인트 일괄 삭제
5
+ export async function DELETE(request: NextRequest) {
6
+ const body = await request.json()
7
+ const { ids } = body
8
+
9
+ if (!Array.isArray(ids) || ids.length === 0) {
10
+ return NextResponse.json(
11
+ { error: 'ids must be a non-empty array' },
12
+ { status: 400 }
13
+ )
14
+ }
15
+
16
+ const result = await prisma.mockEndpoint.deleteMany({
17
+ where: {
18
+ id: { in: ids },
19
+ },
20
+ })
21
+
22
+ return NextResponse.json({ deleted: result.count })
23
+ }
@@ -0,0 +1,62 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { Prisma } from '@/generated/prisma'
3
+ import { prisma } from '@/lib/prisma'
4
+ import { parseOpenAPISpec } from '@/lib/openapi-parser'
5
+
6
+ export async function POST(request: NextRequest) {
7
+ const body = await request.json()
8
+ const { spec, pathPrefix } = body
9
+
10
+ // Handle both old format (spec directly) and new format ({ spec, pathPrefix })
11
+ const actualSpec = spec ?? body
12
+ const endpoints = parseOpenAPISpec(actualSpec)
13
+
14
+ // Apply path prefix if provided
15
+ const normalizedPrefix = pathPrefix ? pathPrefix.replace(/\/+$/, '') : ''
16
+ const processedEndpoints = endpoints.map((ep) => ({
17
+ ...ep,
18
+ path: normalizedPrefix ? `${normalizedPrefix}${ep.path}` : ep.path,
19
+ }))
20
+
21
+ if (processedEndpoints.length === 0) {
22
+ return NextResponse.json(
23
+ { error: 'No valid endpoints found in OpenAPI spec' },
24
+ { status: 400 }
25
+ )
26
+ }
27
+
28
+ const results = await Promise.allSettled(
29
+ processedEndpoints.map((ep) =>
30
+ prisma.mockEndpoint.upsert({
31
+ where: {
32
+ path_method: {
33
+ path: ep.path,
34
+ method: ep.method,
35
+ },
36
+ },
37
+ update: {
38
+ description: ep.description,
39
+ statusCode: ep.statusCode,
40
+ responseBody: ep.responseBody as Prisma.InputJsonValue,
41
+ },
42
+ create: {
43
+ path: ep.path,
44
+ method: ep.method,
45
+ description: ep.description,
46
+ statusCode: ep.statusCode,
47
+ responseBody: ep.responseBody as Prisma.InputJsonValue,
48
+ },
49
+ })
50
+ )
51
+ )
52
+
53
+ const created = results.filter((r) => r.status === 'fulfilled').length
54
+ const failed = results.filter((r) => r.status === 'rejected').length
55
+
56
+ return NextResponse.json({
57
+ message: `Imported ${created} endpoints`,
58
+ created,
59
+ failed,
60
+ total: processedEndpoints.length,
61
+ })
62
+ }
@@ -0,0 +1,39 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { prisma } from '@/lib/prisma'
3
+ import { validatePath, validateMethod, validateStatusCode } from '@/lib/validation'
4
+
5
+ // GET: 모든 엔드포인트 조회
6
+ export async function GET() {
7
+ const endpoints = await prisma.mockEndpoint.findMany({
8
+ orderBy: { createdAt: 'desc' },
9
+ })
10
+ return NextResponse.json(endpoints)
11
+ }
12
+
13
+ // POST: 새 엔드포인트 생성
14
+ export async function POST(request: NextRequest) {
15
+ const body = await request.json()
16
+ const { path, method, description, statusCode, responseBody } = body
17
+
18
+ if (!validatePath(path)) {
19
+ return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
20
+ }
21
+ if (!validateMethod(method)) {
22
+ return NextResponse.json({ error: 'Invalid method' }, { status: 400 })
23
+ }
24
+ if (!validateStatusCode(statusCode)) {
25
+ return NextResponse.json({ error: 'Invalid status code' }, { status: 400 })
26
+ }
27
+
28
+ const endpoint = await prisma.mockEndpoint.create({
29
+ data: {
30
+ path,
31
+ method: method.toUpperCase(),
32
+ description,
33
+ statusCode: statusCode ?? 200,
34
+ responseBody,
35
+ },
36
+ })
37
+
38
+ return NextResponse.json(endpoint, { status: 201 })
39
+ }
@@ -0,0 +1,89 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { Prisma } from '@/generated/prisma'
3
+ import { prisma } from '@/lib/prisma'
4
+ import { parseOpenAPISpec } from '@/lib/openapi-parser'
5
+
6
+ export async function POST(request: NextRequest) {
7
+ const { url, pathPrefix } = await request.json()
8
+
9
+ if (!url) {
10
+ return NextResponse.json({ error: 'URL is required' }, { status: 400 })
11
+ }
12
+
13
+ // Normalize path prefix (remove trailing slashes)
14
+ const normalizedPrefix = pathPrefix ? pathPrefix.replace(/\/+$/, '') : ''
15
+
16
+ try {
17
+ // OpenAPI JSON 가져오기
18
+ const response = await fetch(url, {
19
+ headers: {
20
+ Accept: 'application/json',
21
+ },
22
+ })
23
+
24
+ if (!response.ok) {
25
+ return NextResponse.json(
26
+ { error: `Failed to fetch OpenAPI spec: ${response.statusText}` },
27
+ { status: 400 }
28
+ )
29
+ }
30
+
31
+ const spec = await response.json()
32
+ const endpoints = parseOpenAPISpec(spec)
33
+
34
+ // Apply path prefix if provided
35
+ const processedEndpoints = endpoints.map((ep) => ({
36
+ ...ep,
37
+ path: normalizedPrefix ? `${normalizedPrefix}${ep.path}` : ep.path,
38
+ }))
39
+
40
+ if (processedEndpoints.length === 0) {
41
+ return NextResponse.json(
42
+ { error: 'No valid endpoints found in OpenAPI spec' },
43
+ { status: 400 }
44
+ )
45
+ }
46
+
47
+ // Upsert로 새로 추가하거나 업데이트
48
+ const results = await Promise.allSettled(
49
+ processedEndpoints.map((ep) =>
50
+ prisma.mockEndpoint.upsert({
51
+ where: {
52
+ path_method: {
53
+ path: ep.path,
54
+ method: ep.method,
55
+ },
56
+ },
57
+ update: {
58
+ description: ep.description,
59
+ statusCode: ep.statusCode,
60
+ responseBody: ep.responseBody as Prisma.InputJsonValue,
61
+ },
62
+ create: {
63
+ path: ep.path,
64
+ method: ep.method,
65
+ description: ep.description,
66
+ statusCode: ep.statusCode,
67
+ responseBody: ep.responseBody as Prisma.InputJsonValue,
68
+ },
69
+ })
70
+ )
71
+ )
72
+
73
+ const succeeded = results.filter((r) => r.status === 'fulfilled').length
74
+ const failed = results.filter((r) => r.status === 'rejected').length
75
+
76
+ return NextResponse.json({
77
+ message: `Synced ${succeeded} endpoints`,
78
+ succeeded,
79
+ failed,
80
+ total: processedEndpoints.length,
81
+ })
82
+ } catch (error) {
83
+ console.error('Sync error:', error)
84
+ return NextResponse.json(
85
+ { error: error instanceof Error ? error.message : 'Sync failed' },
86
+ { status: 500 }
87
+ )
88
+ }
89
+ }
@@ -0,0 +1,82 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { match } from 'path-to-regexp'
3
+ import { prisma } from '@/lib/prisma'
4
+
5
+ type RouteParams = {
6
+ params: Promise<{ slug: string[] }>
7
+ }
8
+
9
+ async function findMatchingEndpoint(requestPath: string, method: string) {
10
+ // DB에서 해당 method의 모든 엔드포인트 조회
11
+ const endpoints = await prisma.mockEndpoint.findMany({
12
+ where: { method },
13
+ })
14
+
15
+ // 정확히 일치하는 경로 먼저 확인
16
+ const exactMatch = endpoints.find((ep) => ep.path === requestPath)
17
+ if (exactMatch) {
18
+ return { endpoint: exactMatch, params: {} }
19
+ }
20
+
21
+ // path-to-regexp를 사용한 패턴 매칭
22
+ for (const endpoint of endpoints) {
23
+ try {
24
+ const matchFn = match(endpoint.path, { decode: decodeURIComponent })
25
+ const result = matchFn(requestPath)
26
+
27
+ if (result) {
28
+ return { endpoint, params: result.params }
29
+ }
30
+ } catch {
31
+ // 잘못된 패턴은 무시
32
+ continue
33
+ }
34
+ }
35
+
36
+ return null
37
+ }
38
+
39
+ async function handleRequest(request: NextRequest, { params }: RouteParams) {
40
+ const { slug } = await params
41
+ const requestPath = '/' + slug.join('/')
42
+ const method = request.method
43
+
44
+ const result = await findMatchingEndpoint(requestPath, method)
45
+
46
+ if (!result) {
47
+ return NextResponse.json(
48
+ { error: 'Endpoint not found', path: requestPath, method },
49
+ { status: 404 }
50
+ )
51
+ }
52
+
53
+ const { endpoint, params: pathParams } = result
54
+
55
+ // responseBody에 pathParams를 포함하여 반환 (선택적)
56
+ const responseData =
57
+ Object.keys(pathParams).length > 0
58
+ ? { ...endpoint.responseBody as object, _pathParams: pathParams }
59
+ : endpoint.responseBody
60
+
61
+ return NextResponse.json(responseData, { status: endpoint.statusCode })
62
+ }
63
+
64
+ export async function GET(request: NextRequest, context: RouteParams) {
65
+ return handleRequest(request, context)
66
+ }
67
+
68
+ export async function POST(request: NextRequest, context: RouteParams) {
69
+ return handleRequest(request, context)
70
+ }
71
+
72
+ export async function PUT(request: NextRequest, context: RouteParams) {
73
+ return handleRequest(request, context)
74
+ }
75
+
76
+ export async function PATCH(request: NextRequest, context: RouteParams) {
77
+ return handleRequest(request, context)
78
+ }
79
+
80
+ export async function DELETE(request: NextRequest, context: RouteParams) {
81
+ return handleRequest(request, context)
82
+ }
@@ -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 '@/generated/prisma'
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-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurly-growth/growthman",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Local mock API server with UI dashboard",
5
5
  "bin": {
6
6
  "growthman": "./bin/cli.js"
package/prisma/seed.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { PrismaClient } from '../generated/prisma'
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
+ })
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
+ }
Binary file