swagger-parser-mcp-server 2.0.4 → 2.3.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/README.md +131 -100
- package/dist/application/snapshot/createSnapshotRuntime.d.ts +2 -0
- package/dist/application/snapshot/createSnapshotRuntime.d.ts.map +1 -0
- package/dist/application/snapshot/createSnapshotRuntime.js +2 -0
- package/dist/application/snapshot/createSnapshotRuntime.js.map +1 -0
- package/dist/application/snapshot/snapshotCaptureService.d.ts +90 -0
- package/dist/application/snapshot/snapshotCaptureService.d.ts.map +1 -0
- package/dist/application/snapshot/snapshotCaptureService.js +394 -0
- package/dist/application/snapshot/snapshotCaptureService.js.map +1 -0
- package/dist/application/snapshot/snapshotRepository.d.ts +77 -0
- package/dist/application/snapshot/snapshotRepository.d.ts.map +1 -0
- package/dist/application/snapshot/snapshotRepository.js +2 -0
- package/dist/application/snapshot/snapshotRepository.js.map +1 -0
- package/dist/domain/canonical/canonicalSnapshot.d.ts +61 -0
- package/dist/domain/canonical/canonicalSnapshot.d.ts.map +1 -0
- package/dist/domain/canonical/canonicalSnapshot.js +300 -0
- package/dist/domain/canonical/canonicalSnapshot.js.map +1 -0
- package/dist/domain/contracts/runtimeEnvironmentContract.d.ts +21 -0
- package/dist/domain/contracts/runtimeEnvironmentContract.d.ts.map +1 -0
- package/dist/domain/contracts/runtimeEnvironmentContract.js +50 -0
- package/dist/domain/contracts/runtimeEnvironmentContract.js.map +1 -0
- package/dist/domain/contracts/snapshotDiffContract.d.ts +270 -0
- package/dist/domain/contracts/snapshotDiffContract.d.ts.map +1 -0
- package/dist/domain/contracts/snapshotDiffContract.js +99 -0
- package/dist/domain/contracts/snapshotDiffContract.js.map +1 -0
- package/dist/domain/diff/endpointDiffClassifier.d.ts +78 -0
- package/dist/domain/diff/endpointDiffClassifier.d.ts.map +1 -0
- package/dist/domain/diff/endpointDiffClassifier.js +317 -0
- package/dist/domain/diff/endpointDiffClassifier.js.map +1 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +52 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -266
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/postgres/migrationRunner.d.ts +14 -0
- package/dist/infrastructure/postgres/migrationRunner.d.ts.map +1 -0
- package/dist/infrastructure/postgres/migrationRunner.js +161 -0
- package/dist/infrastructure/postgres/migrationRunner.js.map +1 -0
- package/dist/infrastructure/postgres/migrations/001_snapshot_schema.sql +29 -0
- package/dist/infrastructure/postgres/migrations/002_snapshot_change_history.sql +32 -0
- package/dist/infrastructure/postgres/postgresSnapshotRepository.d.ts +25 -0
- package/dist/infrastructure/postgres/postgresSnapshotRepository.d.ts.map +1 -0
- package/dist/infrastructure/postgres/postgresSnapshotRepository.js +323 -0
- package/dist/infrastructure/postgres/postgresSnapshotRepository.js.map +1 -0
- package/dist/infrastructure/postgres/runMigrations.d.ts +3 -0
- package/dist/infrastructure/postgres/runMigrations.d.ts.map +1 -0
- package/dist/infrastructure/postgres/runMigrations.js +33 -0
- package/dist/infrastructure/postgres/runMigrations.js.map +1 -0
- package/dist/infrastructure/runtime/createSnapshotRuntime.d.ts +17 -0
- package/dist/infrastructure/runtime/createSnapshotRuntime.d.ts.map +1 -0
- package/dist/infrastructure/runtime/createSnapshotRuntime.js +38 -0
- package/dist/infrastructure/runtime/createSnapshotRuntime.js.map +1 -0
- package/dist/schemas.d.ts +284 -3
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +110 -1
- package/dist/schemas.js.map +1 -1
- package/dist/tests/canonicalSnapshot.test.d.ts +2 -0
- package/dist/tests/canonicalSnapshot.test.d.ts.map +1 -0
- package/dist/tests/canonicalSnapshot.test.js +425 -0
- package/dist/tests/canonicalSnapshot.test.js.map +1 -0
- package/dist/tests/endpointDiffClassifier.test.d.ts +2 -0
- package/dist/tests/endpointDiffClassifier.test.d.ts.map +1 -0
- package/dist/tests/endpointDiffClassifier.test.js +633 -0
- package/dist/tests/endpointDiffClassifier.test.js.map +1 -0
- package/dist/tests/httpSnapshotTransport.test.d.ts +2 -0
- package/dist/tests/httpSnapshotTransport.test.d.ts.map +1 -0
- package/dist/tests/httpSnapshotTransport.test.js +356 -0
- package/dist/tests/httpSnapshotTransport.test.js.map +1 -0
- package/dist/tests/indexLifecycle.test.d.ts +2 -0
- package/dist/tests/indexLifecycle.test.d.ts.map +1 -0
- package/dist/tests/indexLifecycle.test.js +23 -0
- package/dist/tests/indexLifecycle.test.js.map +1 -0
- package/dist/tests/mcpSnapshotTools.test.d.ts +2 -0
- package/dist/tests/mcpSnapshotTools.test.d.ts.map +1 -0
- package/dist/tests/mcpSnapshotTools.test.js +316 -0
- package/dist/tests/mcpSnapshotTools.test.js.map +1 -0
- package/dist/tests/postgresMigrationSmoke.test.d.ts +2 -0
- package/dist/tests/postgresMigrationSmoke.test.d.ts.map +1 -0
- package/dist/tests/postgresMigrationSmoke.test.js +187 -0
- package/dist/tests/postgresMigrationSmoke.test.js.map +1 -0
- package/dist/tests/realPostgresTestSchema.d.ts +10 -0
- package/dist/tests/realPostgresTestSchema.d.ts.map +1 -0
- package/dist/tests/realPostgresTestSchema.js +73 -0
- package/dist/tests/realPostgresTestSchema.js.map +1 -0
- package/dist/tests/snapshotCapturePipeline.test.d.ts +2 -0
- package/dist/tests/snapshotCapturePipeline.test.d.ts.map +1 -0
- package/dist/tests/snapshotCapturePipeline.test.js +475 -0
- package/dist/tests/snapshotCapturePipeline.test.js.map +1 -0
- package/dist/tests/snapshotDiffContract.test.d.ts +2 -0
- package/dist/tests/snapshotDiffContract.test.d.ts.map +1 -0
- package/dist/tests/snapshotDiffContract.test.js +156 -0
- package/dist/tests/snapshotDiffContract.test.js.map +1 -0
- package/dist/tests/snapshotPersistence.real.test.d.ts +2 -0
- package/dist/tests/snapshotPersistence.real.test.d.ts.map +1 -0
- package/dist/tests/snapshotPersistence.real.test.js +310 -0
- package/dist/tests/snapshotPersistence.real.test.js.map +1 -0
- package/dist/tests/webServerRuntime.test.d.ts +2 -0
- package/dist/tests/webServerRuntime.test.d.ts.map +1 -0
- package/dist/tests/webServerRuntime.test.js +123 -0
- package/dist/tests/webServerRuntime.test.js.map +1 -0
- package/dist/transport/createSnapshotToolRuntime.d.ts +7 -0
- package/dist/transport/createSnapshotToolRuntime.d.ts.map +1 -0
- package/dist/transport/createSnapshotToolRuntime.js +40 -0
- package/dist/transport/createSnapshotToolRuntime.js.map +1 -0
- package/dist/transport/httpSnapshotServer.d.ts +11 -0
- package/dist/transport/httpSnapshotServer.d.ts.map +1 -0
- package/dist/transport/httpSnapshotServer.js +216 -0
- package/dist/transport/httpSnapshotServer.js.map +1 -0
- package/dist/transport/mcpToolRouter.d.ts +81 -0
- package/dist/transport/mcpToolRouter.d.ts.map +1 -0
- package/dist/transport/mcpToolRouter.js +416 -0
- package/dist/transport/mcpToolRouter.js.map +1 -0
- package/dist/transport/webServerRuntime.d.ts +17 -0
- package/dist/transport/webServerRuntime.d.ts.map +1 -0
- package/dist/transport/webServerRuntime.js +73 -0
- package/dist/transport/webServerRuntime.js.map +1 -0
- package/dist/utils/swaggerCache.d.ts +4 -0
- package/dist/utils/swaggerCache.d.ts.map +1 -1
- package/dist/utils/swaggerCache.js +8 -0
- package/dist/utils/swaggerCache.js.map +1 -1
- package/dist/utils/types.d.ts +2 -1
- package/dist/utils/types.d.ts.map +1 -1
- package/dist/utils/types.js +2 -0
- package/dist/utils/types.js.map +1 -1
- package/dist/web/dashboard.css +411 -0
- package/dist/web/dashboard.html +141 -0
- package/dist/web/dashboard.js +540 -0
- package/package.json +27 -17
package/dist/index.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// swagger-parser-server.ts
|
|
3
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { createSnapshotToolRuntime } from './transport/createSnapshotToolRuntime.js';
|
|
6
|
+
import { startSnapshotWebServer } from './transport/webServerRuntime.js';
|
|
7
|
+
export function attachStdioShutdownHooks(stdin, onClosed) {
|
|
8
|
+
let handled = false;
|
|
9
|
+
const handleClosed = () => {
|
|
10
|
+
if (handled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
handled = true;
|
|
14
|
+
onClosed();
|
|
15
|
+
};
|
|
16
|
+
stdin.once('end', handleClosed);
|
|
17
|
+
stdin.once('close', handleClosed);
|
|
18
|
+
return () => {
|
|
19
|
+
stdin.off('end', handleClosed);
|
|
20
|
+
stdin.off('close', handleClosed);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
10
23
|
class SwaggerParserServer {
|
|
11
24
|
server;
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
runtime = createSnapshotToolRuntime(process.env);
|
|
26
|
+
webServer = null;
|
|
14
27
|
constructor() {
|
|
15
|
-
this.swaggerCache = new SwaggerCache();
|
|
16
|
-
this.schemaResolver = new SchemaResolver({
|
|
17
|
-
maxDepth: 10,
|
|
18
|
-
includeCircularRefs: false, // Prevent stack overflow by default
|
|
19
|
-
circularRefPlaceholder: '[Circular Reference]',
|
|
20
|
-
});
|
|
21
28
|
this.server = new Server({
|
|
22
29
|
name: 'swagger-parser-server',
|
|
23
30
|
version: '1.0.0',
|
|
@@ -29,271 +36,86 @@ class SwaggerParserServer {
|
|
|
29
36
|
this.setupHandlers();
|
|
30
37
|
}
|
|
31
38
|
setupHandlers() {
|
|
32
|
-
// 도구 목록 제공
|
|
33
39
|
this.server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
34
|
-
tools:
|
|
35
|
-
{
|
|
36
|
-
name: 'load_swagger',
|
|
37
|
-
description: 'Swagger/OpenAPI JSON URL을 로드합니다',
|
|
38
|
-
inputSchema: zodToJsonSchema(loadSwaggerSchema),
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'get_controllers',
|
|
42
|
-
description: '모든 컨트롤러(태그) 목록을 가져옵니다',
|
|
43
|
-
inputSchema: zodToJsonSchema(getControllersSchema),
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'get_paths_by_controller',
|
|
47
|
-
description: '특정 컨트롤러의 모든 path를 가져옵니다',
|
|
48
|
-
inputSchema: zodToJsonSchema(getPathsByControllerSchema),
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'get_path_details',
|
|
52
|
-
description: '특정 path의 상세 정보를 가져옵니다',
|
|
53
|
-
inputSchema: zodToJsonSchema(getPathDetailsSchema),
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'resolve_schema',
|
|
57
|
-
description: '$ref를 해결하여 실제 스키마를 가져옵니다',
|
|
58
|
-
inputSchema: zodToJsonSchema(resolveSchemaSchema),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
40
|
+
tools: this.runtime.toolRouter.listTools(),
|
|
61
41
|
}));
|
|
62
|
-
// 도구 실행 핸들러
|
|
63
42
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
64
43
|
const { name, arguments: args } = request.params;
|
|
65
|
-
|
|
66
|
-
switch (name) {
|
|
67
|
-
case 'load_swagger': {
|
|
68
|
-
const validatedArgs = loadSwaggerSchema.parse(args);
|
|
69
|
-
return await this.loadSwagger(validatedArgs.url);
|
|
70
|
-
}
|
|
71
|
-
case 'get_controllers': {
|
|
72
|
-
const validatedArgs = getControllersSchema.parse(args);
|
|
73
|
-
return await this.getControllers(validatedArgs.url);
|
|
74
|
-
}
|
|
75
|
-
case 'get_paths_by_controller': {
|
|
76
|
-
const validatedArgs = getPathsByControllerSchema.parse(args);
|
|
77
|
-
return await this.getPathsByController(validatedArgs.url, validatedArgs.controller);
|
|
78
|
-
}
|
|
79
|
-
case 'get_path_details': {
|
|
80
|
-
const validatedArgs = getPathDetailsSchema.parse(args);
|
|
81
|
-
return await this.getPathDetails(validatedArgs.url, validatedArgs.path, validatedArgs.method);
|
|
82
|
-
}
|
|
83
|
-
case 'resolve_schema': {
|
|
84
|
-
const validatedArgs = resolveSchemaSchema.parse(args);
|
|
85
|
-
return await this.resolveSchema(validatedArgs.url, validatedArgs.ref);
|
|
86
|
-
}
|
|
87
|
-
default:
|
|
88
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
93
|
-
return {
|
|
94
|
-
content: [
|
|
95
|
-
{
|
|
96
|
-
type: 'text',
|
|
97
|
-
text: `Error: ${errorMessage}`,
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
44
|
+
return this.runtime.toolRouter.execute(name, args);
|
|
102
45
|
});
|
|
103
46
|
}
|
|
104
|
-
async
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: 'text',
|
|
111
|
-
text: JSON.stringify({
|
|
112
|
-
success: true,
|
|
113
|
-
info: swagger.info,
|
|
114
|
-
pathCount: Object.keys(swagger.paths).length,
|
|
115
|
-
hasComponents: !!swagger.components,
|
|
116
|
-
}, null, 2),
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
123
|
-
throw new Error(`Failed to load Swagger: ${errorMessage}`);
|
|
47
|
+
async run(onTransportClosed) {
|
|
48
|
+
const transport = new StdioServerTransport();
|
|
49
|
+
if (onTransportClosed) {
|
|
50
|
+
transport.onclose = onTransportClosed;
|
|
124
51
|
}
|
|
52
|
+
await this.server.connect(transport);
|
|
53
|
+
console.error('Swagger Parser MCP Server running on stdio');
|
|
125
54
|
}
|
|
126
|
-
async
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const swagger = await this.getSwagger(url);
|
|
131
|
-
const controllers = new Set();
|
|
132
|
-
Object.values(swagger.paths || {}).forEach(pathItem => {
|
|
133
|
-
if (!pathItem)
|
|
134
|
-
return;
|
|
135
|
-
// Check each HTTP method
|
|
136
|
-
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
137
|
-
methods.forEach(method => {
|
|
138
|
-
const operation = pathItem[method];
|
|
139
|
-
if (operation?.tags) {
|
|
140
|
-
operation.tags.forEach((tag) => controllers.add(tag));
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
return {
|
|
145
|
-
content: [
|
|
146
|
-
{
|
|
147
|
-
type: 'text',
|
|
148
|
-
text: JSON.stringify({
|
|
149
|
-
controllers: Array.from(controllers),
|
|
150
|
-
count: controllers.size,
|
|
151
|
-
}, null, 2),
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
async getPathsByController(url, controller) {
|
|
157
|
-
const swagger = await this.getSwagger(url);
|
|
158
|
-
const paths = [];
|
|
159
|
-
Object.entries(swagger.paths || {}).forEach(([path, pathItem]) => {
|
|
160
|
-
if (!pathItem)
|
|
161
|
-
return;
|
|
162
|
-
// Check each HTTP method
|
|
163
|
-
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
164
|
-
methods.forEach(method => {
|
|
165
|
-
const operation = pathItem[method];
|
|
166
|
-
if (operation?.tags?.includes(controller)) {
|
|
167
|
-
paths.push({
|
|
168
|
-
path,
|
|
169
|
-
method,
|
|
170
|
-
operationId: operation.operationId,
|
|
171
|
-
summary: operation.summary,
|
|
172
|
-
description: operation.description,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
});
|
|
55
|
+
async startWebServer() {
|
|
56
|
+
this.webServer = await startSnapshotWebServer({
|
|
57
|
+
env: process.env,
|
|
58
|
+
toolRouter: this.runtime.toolRouter,
|
|
176
59
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
controller,
|
|
183
|
-
paths,
|
|
184
|
-
count: paths.length,
|
|
185
|
-
}, null, 2),
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
};
|
|
60
|
+
if (this.webServer.enabled) {
|
|
61
|
+
console.error(`Swagger Snapshot Web Server running on http://${this.webServer.host}:${this.webServer.port}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.error('Swagger Snapshot Web Server is disabled (SNAPSHOT_WEB_ENABLED=false)');
|
|
189
65
|
}
|
|
190
|
-
async
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!operation || typeof operation !== 'object') {
|
|
195
|
-
throw new Error(`Path ${method.toUpperCase()} ${path} not found`);
|
|
66
|
+
async dispose() {
|
|
67
|
+
if (this.webServer) {
|
|
68
|
+
await this.webServer.close().catch(() => undefined);
|
|
69
|
+
this.webServer = null;
|
|
196
70
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return
|
|
211
|
-
name: param.name,
|
|
212
|
-
in: param.in,
|
|
213
|
-
required: param.required,
|
|
214
|
-
description: param.description,
|
|
215
|
-
schema: this.schemaResolver.resolveSchemaReference(param.schema, swagger),
|
|
216
|
-
};
|
|
217
|
-
}) || [];
|
|
218
|
-
// Request Body 정보 추출
|
|
219
|
-
let requestBody = null;
|
|
220
|
-
if (operation.requestBody && 'content' in operation.requestBody && operation.requestBody.content) {
|
|
221
|
-
const content = operation.requestBody.content;
|
|
222
|
-
const mediaType = Object.keys(content)[0];
|
|
223
|
-
const mediaTypeObj = content[mediaType];
|
|
224
|
-
if (mediaTypeObj?.schema) {
|
|
225
|
-
requestBody = {
|
|
226
|
-
required: operation.requestBody.required,
|
|
227
|
-
mediaType,
|
|
228
|
-
schema: this.schemaResolver.resolveSchemaReference(mediaTypeObj.schema, swagger),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
71
|
+
await this.runtime.dispose();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function isDirectExecution() {
|
|
75
|
+
const entryPoint = process.argv[1] ?? '';
|
|
76
|
+
return /(?:^|[/\\])index\.(?:ts|js)$/.test(entryPoint);
|
|
77
|
+
}
|
|
78
|
+
if (isDirectExecution()) {
|
|
79
|
+
const server = new SwaggerParserServer();
|
|
80
|
+
let shuttingDown = false;
|
|
81
|
+
let detachStdioShutdownHooks = null;
|
|
82
|
+
const shutdown = async (exitCode) => {
|
|
83
|
+
if (shuttingDown) {
|
|
84
|
+
return;
|
|
231
85
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (response && typeof response === 'object' && 'content' in response && response.content) {
|
|
237
|
-
const content = response.content;
|
|
238
|
-
const mediaType = Object.keys(content)[0];
|
|
239
|
-
const mediaTypeObj = content[mediaType];
|
|
240
|
-
if (mediaTypeObj?.schema) {
|
|
241
|
-
responses[statusCode] = {
|
|
242
|
-
description: response.description,
|
|
243
|
-
mediaType,
|
|
244
|
-
schema: this.schemaResolver.resolveSchemaReference(mediaTypeObj.schema, swagger),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else if (response && typeof response === 'object' && 'description' in response) {
|
|
249
|
-
responses[statusCode] = {
|
|
250
|
-
description: response.description,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
});
|
|
86
|
+
shuttingDown = true;
|
|
87
|
+
if (detachStdioShutdownHooks) {
|
|
88
|
+
detachStdioShutdownHooks();
|
|
89
|
+
detachStdioShutdownHooks = null;
|
|
254
90
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
resolved,
|
|
285
|
-
}, null, 2),
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
async run() {
|
|
291
|
-
const transport = new StdioServerTransport();
|
|
292
|
-
await this.server.connect(transport);
|
|
293
|
-
console.error('Swagger Parser MCP Server running on stdio');
|
|
294
|
-
}
|
|
91
|
+
await server.dispose().catch(() => undefined);
|
|
92
|
+
process.exit(exitCode);
|
|
93
|
+
};
|
|
94
|
+
process.on('SIGINT', () => {
|
|
95
|
+
void shutdown(0);
|
|
96
|
+
});
|
|
97
|
+
process.on('SIGTERM', () => {
|
|
98
|
+
void shutdown(0);
|
|
99
|
+
});
|
|
100
|
+
process.on('uncaughtException', error => {
|
|
101
|
+
console.error(error);
|
|
102
|
+
void shutdown(1);
|
|
103
|
+
});
|
|
104
|
+
process.on('unhandledRejection', reason => {
|
|
105
|
+
console.error(reason);
|
|
106
|
+
void shutdown(1);
|
|
107
|
+
});
|
|
108
|
+
detachStdioShutdownHooks = attachStdioShutdownHooks(process.stdin, () => {
|
|
109
|
+
void shutdown(0);
|
|
110
|
+
});
|
|
111
|
+
server
|
|
112
|
+
.startWebServer()
|
|
113
|
+
.then(() => server.run(() => {
|
|
114
|
+
void shutdown(0);
|
|
115
|
+
}))
|
|
116
|
+
.catch(error => {
|
|
117
|
+
console.error(error);
|
|
118
|
+
void shutdown(1);
|
|
119
|
+
});
|
|
295
120
|
}
|
|
296
|
-
// 서버 실행
|
|
297
|
-
const server = new SwaggerParserServer();
|
|
298
|
-
server.run().catch(console.error);
|
|
299
121
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
type Queryable = Pick<Pool, 'query'>;
|
|
3
|
+
export interface MigrationRunOptions {
|
|
4
|
+
migrationsDirectory?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MigrationRunSummary {
|
|
7
|
+
migrationsDirectory: string;
|
|
8
|
+
applied: string[];
|
|
9
|
+
skipped: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function runPostgresMigrations(db: Queryable, options?: MigrationRunOptions): Promise<MigrationRunSummary>;
|
|
12
|
+
export declare function getDefaultMigrationsDirectory(): string;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=migrationRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrationRunner.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/postgres/migrationRunner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,KAAK,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAYrC,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA0JD,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,SAAS,EACb,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,mBAAmB,CAAC,CA+B9B;AAED,wBAAgB,6BAA6B,IAAI,MAAM,CAEtD"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function resolveDefaultMigrationsDirectory() {
|
|
5
|
+
const envOverride = process.env.SNAPSHOT_MIGRATIONS_DIR?.trim();
|
|
6
|
+
if (envOverride) {
|
|
7
|
+
return envOverride;
|
|
8
|
+
}
|
|
9
|
+
const entrypointCandidates = [];
|
|
10
|
+
const entrypoint = process.argv[1];
|
|
11
|
+
if (entrypoint && path.isAbsolute(entrypoint)) {
|
|
12
|
+
const entrypointDirectory = path.dirname(entrypoint);
|
|
13
|
+
entrypointCandidates.push(path.join(entrypointDirectory, 'migrations'));
|
|
14
|
+
entrypointCandidates.push(path.join(entrypointDirectory, 'infrastructure', 'postgres', 'migrations'));
|
|
15
|
+
const segments = entrypoint.split(path.sep);
|
|
16
|
+
const distIndex = segments.lastIndexOf('dist');
|
|
17
|
+
if (distIndex >= 0) {
|
|
18
|
+
const distRoot = segments.slice(0, distIndex + 1).join(path.sep);
|
|
19
|
+
entrypointCandidates.push(path.join(distRoot, 'infrastructure', 'postgres', 'migrations'));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const candidateDirectories = [
|
|
23
|
+
...entrypointCandidates,
|
|
24
|
+
path.join(process.cwd(), 'src', 'infrastructure', 'postgres', 'migrations'),
|
|
25
|
+
path.join(process.cwd(), 'dist', 'infrastructure', 'postgres', 'migrations'),
|
|
26
|
+
];
|
|
27
|
+
const existingDirectory = candidateDirectories.find(directory => existsSync(directory));
|
|
28
|
+
return existingDirectory ?? candidateDirectories[0];
|
|
29
|
+
}
|
|
30
|
+
const DEFAULT_MIGRATIONS_DIRECTORY = resolveDefaultMigrationsDirectory();
|
|
31
|
+
function isSqlMigrationFile(filename) {
|
|
32
|
+
return /^\d+.*\.sql$/i.test(filename);
|
|
33
|
+
}
|
|
34
|
+
function compareMigrationFilename(left, right) {
|
|
35
|
+
const parse = (filename) => {
|
|
36
|
+
const match = filename.match(/^(\d+)(?:[-_]?(.+))?\.sql$/i);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return {
|
|
39
|
+
numericVersion: Number.MAX_SAFE_INTEGER,
|
|
40
|
+
suffix: filename.toLowerCase(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
numericVersion: Number.parseInt(match[1], 10),
|
|
45
|
+
suffix: (match[2] ?? '').toLowerCase(),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const leftKey = parse(left);
|
|
49
|
+
const rightKey = parse(right);
|
|
50
|
+
if (leftKey.numericVersion !== rightKey.numericVersion) {
|
|
51
|
+
return leftKey.numericVersion - rightKey.numericVersion;
|
|
52
|
+
}
|
|
53
|
+
const suffixCompare = leftKey.suffix.localeCompare(rightKey.suffix);
|
|
54
|
+
if (suffixCompare !== 0) {
|
|
55
|
+
return suffixCompare;
|
|
56
|
+
}
|
|
57
|
+
return left.localeCompare(right);
|
|
58
|
+
}
|
|
59
|
+
async function ensureMigrationHistoryTable(db) {
|
|
60
|
+
await db.query(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
62
|
+
version TEXT PRIMARY KEY,
|
|
63
|
+
filename TEXT NOT NULL,
|
|
64
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
65
|
+
);
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
async function getAppliedMigrationVersions(db) {
|
|
69
|
+
const result = await db.query(`
|
|
70
|
+
SELECT version
|
|
71
|
+
FROM schema_migrations
|
|
72
|
+
ORDER BY version ASC;
|
|
73
|
+
`);
|
|
74
|
+
return new Set(result.rows.map(row => row.version));
|
|
75
|
+
}
|
|
76
|
+
async function loadMigrationFiles(migrationsDirectory) {
|
|
77
|
+
let directoryEntries;
|
|
78
|
+
try {
|
|
79
|
+
directoryEntries = await readdir(migrationsDirectory, { withFileTypes: true, encoding: 'utf8' });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const code = error?.code;
|
|
83
|
+
if (code === 'ENOENT') {
|
|
84
|
+
throw new Error(`Migration directory not found: ${migrationsDirectory}. Set SNAPSHOT_MIGRATIONS_DIR or ensure migrations are packaged.`);
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
const filenames = directoryEntries
|
|
89
|
+
.filter(entry => entry.isFile() && isSqlMigrationFile(entry.name))
|
|
90
|
+
.map(entry => entry.name)
|
|
91
|
+
.sort(compareMigrationFilename);
|
|
92
|
+
if (filenames.length === 0) {
|
|
93
|
+
throw new Error(`No SQL migration files found in directory: ${migrationsDirectory}`);
|
|
94
|
+
}
|
|
95
|
+
const files = [];
|
|
96
|
+
for (const filename of filenames) {
|
|
97
|
+
const sql = await readFile(path.join(migrationsDirectory, filename), 'utf8');
|
|
98
|
+
files.push({
|
|
99
|
+
version: filename,
|
|
100
|
+
filename,
|
|
101
|
+
sql,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return files;
|
|
105
|
+
}
|
|
106
|
+
async function applyMigration(db, migration) {
|
|
107
|
+
await db.query('BEGIN');
|
|
108
|
+
try {
|
|
109
|
+
await db.query(migration.sql);
|
|
110
|
+
const insertResult = await db.query(`
|
|
111
|
+
INSERT INTO schema_migrations (version, filename)
|
|
112
|
+
VALUES ($1, $2);
|
|
113
|
+
`, [migration.version, migration.filename]);
|
|
114
|
+
if (insertResult.rowCount !== 1) {
|
|
115
|
+
throw new Error(`Failed to persist migration history row for ${migration.filename}.`);
|
|
116
|
+
}
|
|
117
|
+
await db.query('COMMIT');
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
122
|
+
if (/duplicate key/i.test(message) || /already exists/i.test(message)) {
|
|
123
|
+
await db.query('ROLLBACK');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
await db.query('ROLLBACK');
|
|
127
|
+
throw new Error(`Failed to apply migration ${migration.filename}: ${message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export async function runPostgresMigrations(db, options = {}) {
|
|
131
|
+
const migrationsDirectory = options.migrationsDirectory ?? DEFAULT_MIGRATIONS_DIRECTORY;
|
|
132
|
+
await ensureMigrationHistoryTable(db);
|
|
133
|
+
const migrations = await loadMigrationFiles(migrationsDirectory);
|
|
134
|
+
const appliedVersions = await getAppliedMigrationVersions(db);
|
|
135
|
+
const applied = [];
|
|
136
|
+
const skipped = [];
|
|
137
|
+
for (const migration of migrations) {
|
|
138
|
+
if (appliedVersions.has(migration.version)) {
|
|
139
|
+
skipped.push(migration.filename);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const created = await applyMigration(db, migration);
|
|
143
|
+
if (created) {
|
|
144
|
+
applied.push(migration.filename);
|
|
145
|
+
appliedVersions.add(migration.version);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
skipped.push(migration.filename);
|
|
149
|
+
appliedVersions.add(migration.version);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
migrationsDirectory,
|
|
154
|
+
applied,
|
|
155
|
+
skipped,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function getDefaultMigrationsDirectory() {
|
|
159
|
+
return DEFAULT_MIGRATIONS_DIRECTORY;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=migrationRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrationRunner.js","sourceRoot":"","sources":["../../../src/infrastructure/postgres/migrationRunner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
2
|
+
id BIGSERIAL PRIMARY KEY,
|
|
3
|
+
project_key TEXT NOT NULL UNIQUE,
|
|
4
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
8
|
+
id BIGSERIAL PRIMARY KEY,
|
|
9
|
+
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
10
|
+
swagger_url TEXT NOT NULL,
|
|
11
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
12
|
+
UNIQUE (project_id, swagger_url)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
16
|
+
id BIGSERIAL PRIMARY KEY,
|
|
17
|
+
source_id BIGINT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
18
|
+
spec_hash TEXT NOT NULL,
|
|
19
|
+
document_fingerprint TEXT NOT NULL,
|
|
20
|
+
canonical_snapshot JSONB NOT NULL,
|
|
21
|
+
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
22
|
+
UNIQUE (source_id, spec_hash)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_source_captured_desc
|
|
26
|
+
ON snapshots (source_id, captured_at DESC, id DESC);
|
|
27
|
+
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_source_spec_hash
|
|
29
|
+
ON snapshots (source_id, spec_hash);
|