@jgardner04/ghost-mcp-server 1.11.0 → 1.12.1
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 +199 -43
- package/package.json +2 -1
- package/src/__tests__/mcp_server.test.js +10 -4
- package/src/__tests__/mcp_server_improved.test.js +261 -156
- package/src/__tests__/mcp_server_pages.test.js +72 -68
- package/src/errors/__tests__/index.test.js +70 -0
- package/src/errors/index.js +10 -0
- package/src/mcp_server.js +9 -19
- package/src/mcp_server_improved.js +716 -531
- package/src/schemas/__tests__/common.test.js +84 -0
- package/src/schemas/__tests__/memberSchemas.test.js +447 -0
- package/src/schemas/__tests__/newsletterSchemas.test.js +399 -0
- package/src/schemas/__tests__/pageSchemas.test.js +518 -0
- package/src/schemas/__tests__/tierSchemas.test.js +574 -0
- package/src/schemas/common.js +50 -3
- package/src/services/__tests__/ghostServiceImproved.members.test.js +12 -61
- package/src/services/__tests__/postService.test.js +7 -99
- package/src/services/ghostServiceImproved.js +10 -21
- package/src/services/postService.js +4 -30
- package/src/utils/__tests__/tempFileManager.test.js +316 -0
- package/src/utils/__tests__/validation.test.js +163 -0
- package/src/utils/tempFileManager.js +113 -0
- package/src/utils/validation.js +28 -0
|
@@ -143,12 +143,11 @@ describe('mcp_server_improved - ghost_get_pages tool', () => {
|
|
|
143
143
|
expect(tool).toBeDefined();
|
|
144
144
|
expect(tool.description).toContain('pages');
|
|
145
145
|
expect(tool.schema).toBeDefined();
|
|
146
|
-
expect(tool.schema.limit).toBeDefined();
|
|
147
|
-
expect(tool.schema.page).toBeDefined();
|
|
148
|
-
expect(tool.schema.
|
|
149
|
-
expect(tool.schema.
|
|
150
|
-
expect(tool.schema.
|
|
151
|
-
expect(tool.schema.order).toBeDefined();
|
|
146
|
+
expect(tool.schema.shape.limit).toBeDefined();
|
|
147
|
+
expect(tool.schema.shape.page).toBeDefined();
|
|
148
|
+
expect(tool.schema.shape.include).toBeDefined();
|
|
149
|
+
expect(tool.schema.shape.filter).toBeDefined();
|
|
150
|
+
expect(tool.schema.shape.order).toBeDefined();
|
|
152
151
|
});
|
|
153
152
|
|
|
154
153
|
it('should retrieve pages with default options', async () => {
|
|
@@ -180,20 +179,20 @@ describe('mcp_server_improved - ghost_get_pages tool', () => {
|
|
|
180
179
|
const tool = mockTools.get('ghost_get_pages');
|
|
181
180
|
const schema = tool.schema;
|
|
182
181
|
|
|
183
|
-
expect(schema.limit).toBeDefined();
|
|
184
|
-
expect(() => schema.limit.parse(0)).toThrow();
|
|
185
|
-
expect(() => schema.limit.parse(101)).toThrow();
|
|
186
|
-
expect(schema.limit.parse(50)).toBe(50);
|
|
182
|
+
expect(schema.shape.limit).toBeDefined();
|
|
183
|
+
expect(() => schema.shape.limit.parse(0)).toThrow();
|
|
184
|
+
expect(() => schema.shape.limit.parse(101)).toThrow();
|
|
185
|
+
expect(schema.shape.limit.parse(50)).toBe(50);
|
|
187
186
|
});
|
|
188
187
|
|
|
189
|
-
it('should pass
|
|
188
|
+
it('should pass filter parameter', async () => {
|
|
190
189
|
const mockPages = [{ id: '1', title: 'Published Page', status: 'published' }];
|
|
191
190
|
mockGetPages.mockResolvedValue(mockPages);
|
|
192
191
|
|
|
193
192
|
const tool = mockTools.get('ghost_get_pages');
|
|
194
|
-
await tool.handler({
|
|
193
|
+
await tool.handler({ filter: 'status:published' });
|
|
195
194
|
|
|
196
|
-
expect(mockGetPages).toHaveBeenCalledWith({
|
|
195
|
+
expect(mockGetPages).toHaveBeenCalledWith({ filter: 'status:published' });
|
|
197
196
|
});
|
|
198
197
|
|
|
199
198
|
it('should handle errors gracefully', async () => {
|
|
@@ -222,24 +221,26 @@ describe('mcp_server_improved - ghost_get_page tool', () => {
|
|
|
222
221
|
it('should have correct schema with id and slug options', () => {
|
|
223
222
|
const tool = mockTools.get('ghost_get_page');
|
|
224
223
|
expect(tool).toBeDefined();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
expect(
|
|
224
|
+
// ghost_get_page uses a refined schema, access via _def.schema.shape
|
|
225
|
+
const shape = tool.schema._def.schema.shape;
|
|
226
|
+
expect(shape.id).toBeDefined();
|
|
227
|
+
expect(shape.slug).toBeDefined();
|
|
228
|
+
expect(shape.include).toBeDefined();
|
|
228
229
|
});
|
|
229
230
|
|
|
230
231
|
it('should retrieve page by ID', async () => {
|
|
231
|
-
const mockPage = { id: '
|
|
232
|
+
const mockPage = { id: '507f1f77bcf86cd799439011', title: 'About Us', slug: 'about-us' };
|
|
232
233
|
mockGetPage.mockResolvedValue(mockPage);
|
|
233
234
|
|
|
234
235
|
const tool = mockTools.get('ghost_get_page');
|
|
235
|
-
const result = await tool.handler({ id: '
|
|
236
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
236
237
|
|
|
237
|
-
expect(mockGetPage).toHaveBeenCalledWith('
|
|
238
|
+
expect(mockGetPage).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
238
239
|
expect(result.content[0].text).toContain('About Us');
|
|
239
240
|
});
|
|
240
241
|
|
|
241
242
|
it('should retrieve page by slug', async () => {
|
|
242
|
-
const mockPage = { id: '
|
|
243
|
+
const mockPage = { id: '507f1f77bcf86cd799439011', title: 'About Us', slug: 'about-us' };
|
|
243
244
|
mockGetPage.mockResolvedValue(mockPage);
|
|
244
245
|
|
|
245
246
|
const tool = mockTools.get('ghost_get_page');
|
|
@@ -249,19 +250,20 @@ describe('mcp_server_improved - ghost_get_page tool', () => {
|
|
|
249
250
|
expect(result.content[0].text).toContain('About Us');
|
|
250
251
|
});
|
|
251
252
|
|
|
252
|
-
it('should require either id or slug',
|
|
253
|
+
it('should require either id or slug', () => {
|
|
253
254
|
const tool = mockTools.get('ghost_get_page');
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
expect(
|
|
255
|
+
// Test schema validation - the refine check requires id or slug
|
|
256
|
+
expect(() => tool.schema.parse({})).toThrow();
|
|
257
|
+
// Valid inputs should parse successfully (Ghost IDs are 24 hex chars)
|
|
258
|
+
expect(() => tool.schema.parse({ id: '507f1f77bcf86cd799439011' })).not.toThrow();
|
|
259
|
+
expect(() => tool.schema.parse({ slug: 'about-us' })).not.toThrow();
|
|
258
260
|
});
|
|
259
261
|
|
|
260
262
|
it('should handle errors gracefully', async () => {
|
|
261
263
|
mockGetPage.mockRejectedValue(new Error('Page not found'));
|
|
262
264
|
|
|
263
265
|
const tool = mockTools.get('ghost_get_page');
|
|
264
|
-
const result = await tool.handler({ id: '
|
|
266
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
265
267
|
|
|
266
268
|
expect(result.isError).toBe(true);
|
|
267
269
|
expect(result.content[0].text).toContain('Error retrieving page');
|
|
@@ -283,28 +285,29 @@ describe('mcp_server_improved - ghost_create_page tool', () => {
|
|
|
283
285
|
it('should have correct schema with required and optional fields', () => {
|
|
284
286
|
const tool = mockTools.get('ghost_create_page');
|
|
285
287
|
expect(tool).toBeDefined();
|
|
286
|
-
expect(tool.description).toContain('
|
|
287
|
-
expect(tool.schema.title).toBeDefined();
|
|
288
|
-
expect(tool.schema.html).toBeDefined();
|
|
289
|
-
expect(tool.schema.status).toBeDefined();
|
|
290
|
-
expect(tool.schema.feature_image).toBeDefined();
|
|
291
|
-
expect(tool.schema.meta_title).toBeDefined();
|
|
292
|
-
expect(tool.schema.meta_description).toBeDefined();
|
|
293
|
-
// Should NOT have tags
|
|
294
|
-
expect(tool.schema.tags).toBeUndefined();
|
|
288
|
+
expect(tool.description).toContain('page');
|
|
289
|
+
expect(tool.schema.shape.title).toBeDefined();
|
|
290
|
+
expect(tool.schema.shape.html).toBeDefined();
|
|
291
|
+
expect(tool.schema.shape.status).toBeDefined();
|
|
292
|
+
expect(tool.schema.shape.feature_image).toBeDefined();
|
|
293
|
+
expect(tool.schema.shape.meta_title).toBeDefined();
|
|
294
|
+
expect(tool.schema.shape.meta_description).toBeDefined();
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
it('should create page with minimal input', async () => {
|
|
298
|
-
const createdPage = { id: '
|
|
298
|
+
const createdPage = { id: '507f1f77bcf86cd799439011', title: 'New Page', status: 'draft' };
|
|
299
299
|
mockCreatePageService.mockResolvedValue(createdPage);
|
|
300
300
|
|
|
301
301
|
const tool = mockTools.get('ghost_create_page');
|
|
302
302
|
const result = await tool.handler({ title: 'New Page', html: '<p>Content</p>' });
|
|
303
303
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
// Schema adds default values, so use objectContaining for the key fields
|
|
305
|
+
expect(mockCreatePageService).toHaveBeenCalledWith(
|
|
306
|
+
expect.objectContaining({
|
|
307
|
+
title: 'New Page',
|
|
308
|
+
html: '<p>Content</p>',
|
|
309
|
+
})
|
|
310
|
+
);
|
|
308
311
|
expect(result.content[0].text).toContain('New Page');
|
|
309
312
|
});
|
|
310
313
|
|
|
@@ -320,13 +323,14 @@ describe('mcp_server_improved - ghost_create_page tool', () => {
|
|
|
320
323
|
meta_title: 'SEO Title',
|
|
321
324
|
meta_description: 'SEO Description',
|
|
322
325
|
};
|
|
323
|
-
const createdPage = { id: '
|
|
326
|
+
const createdPage = { id: '507f1f77bcf86cd799439011', ...fullInput };
|
|
324
327
|
mockCreatePageService.mockResolvedValue(createdPage);
|
|
325
328
|
|
|
326
329
|
const tool = mockTools.get('ghost_create_page');
|
|
327
330
|
const result = await tool.handler(fullInput);
|
|
328
331
|
|
|
329
|
-
|
|
332
|
+
// Schema adds default values, so use objectContaining for the key fields
|
|
333
|
+
expect(mockCreatePageService).toHaveBeenCalledWith(expect.objectContaining(fullInput));
|
|
330
334
|
expect(result.content[0].text).toContain('Complete Page');
|
|
331
335
|
});
|
|
332
336
|
|
|
@@ -356,39 +360,39 @@ describe('mcp_server_improved - ghost_update_page tool', () => {
|
|
|
356
360
|
it('should have correct schema with id required and other fields optional', () => {
|
|
357
361
|
const tool = mockTools.get('ghost_update_page');
|
|
358
362
|
expect(tool).toBeDefined();
|
|
359
|
-
expect(tool.description).toContain('
|
|
360
|
-
expect(tool.schema.id).toBeDefined();
|
|
361
|
-
expect(tool.schema.title).toBeDefined();
|
|
362
|
-
expect(tool.schema.html).toBeDefined();
|
|
363
|
-
expect(tool.schema.status).toBeDefined();
|
|
364
|
-
// Should NOT have tags
|
|
365
|
-
expect(tool.schema.tags).toBeUndefined();
|
|
363
|
+
expect(tool.description).toContain('page');
|
|
364
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
365
|
+
expect(tool.schema.shape.title).toBeDefined();
|
|
366
|
+
expect(tool.schema.shape.html).toBeDefined();
|
|
367
|
+
expect(tool.schema.shape.status).toBeDefined();
|
|
366
368
|
});
|
|
367
369
|
|
|
368
370
|
it('should update page with new title', async () => {
|
|
369
|
-
const updatedPage = { id: '
|
|
371
|
+
const updatedPage = { id: '507f1f77bcf86cd799439011', title: 'Updated Title' };
|
|
370
372
|
mockUpdatePage.mockResolvedValue(updatedPage);
|
|
371
373
|
|
|
372
374
|
const tool = mockTools.get('ghost_update_page');
|
|
373
|
-
const result = await tool.handler({ id: '
|
|
375
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Title' });
|
|
374
376
|
|
|
375
|
-
expect(mockUpdatePage).toHaveBeenCalledWith('
|
|
377
|
+
expect(mockUpdatePage).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
378
|
+
title: 'Updated Title',
|
|
379
|
+
});
|
|
376
380
|
expect(result.content[0].text).toContain('Updated Title');
|
|
377
381
|
});
|
|
378
382
|
|
|
379
383
|
it('should update page with multiple fields', async () => {
|
|
380
|
-
const updatedPage = { id: '
|
|
384
|
+
const updatedPage = { id: '507f1f77bcf86cd799439011', title: 'New Title', status: 'published' };
|
|
381
385
|
mockUpdatePage.mockResolvedValue(updatedPage);
|
|
382
386
|
|
|
383
387
|
const tool = mockTools.get('ghost_update_page');
|
|
384
388
|
const result = await tool.handler({
|
|
385
|
-
id: '
|
|
389
|
+
id: '507f1f77bcf86cd799439011',
|
|
386
390
|
title: 'New Title',
|
|
387
391
|
status: 'published',
|
|
388
392
|
html: '<p>Updated content</p>',
|
|
389
393
|
});
|
|
390
394
|
|
|
391
|
-
expect(mockUpdatePage).toHaveBeenCalledWith('
|
|
395
|
+
expect(mockUpdatePage).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
392
396
|
title: 'New Title',
|
|
393
397
|
status: 'published',
|
|
394
398
|
html: '<p>Updated content</p>',
|
|
@@ -400,7 +404,7 @@ describe('mcp_server_improved - ghost_update_page tool', () => {
|
|
|
400
404
|
mockUpdatePage.mockRejectedValue(new Error('Page not found'));
|
|
401
405
|
|
|
402
406
|
const tool = mockTools.get('ghost_update_page');
|
|
403
|
-
const result = await tool.handler({ id: '
|
|
407
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', title: 'Test' });
|
|
404
408
|
|
|
405
409
|
expect(result.isError).toBe(true);
|
|
406
410
|
expect(result.content[0].text).toContain('Error updating page');
|
|
@@ -422,17 +426,17 @@ describe('mcp_server_improved - ghost_delete_page tool', () => {
|
|
|
422
426
|
it('should have correct schema with id required', () => {
|
|
423
427
|
const tool = mockTools.get('ghost_delete_page');
|
|
424
428
|
expect(tool).toBeDefined();
|
|
425
|
-
expect(tool.schema.id).toBeDefined();
|
|
429
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
426
430
|
expect(tool.description).toContain('permanent');
|
|
427
431
|
});
|
|
428
432
|
|
|
429
433
|
it('should delete page by ID', async () => {
|
|
430
|
-
mockDeletePage.mockResolvedValue({ id: '
|
|
434
|
+
mockDeletePage.mockResolvedValue({ id: '507f1f77bcf86cd799439011' });
|
|
431
435
|
|
|
432
436
|
const tool = mockTools.get('ghost_delete_page');
|
|
433
|
-
const result = await tool.handler({ id: '
|
|
437
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
434
438
|
|
|
435
|
-
expect(mockDeletePage).toHaveBeenCalledWith('
|
|
439
|
+
expect(mockDeletePage).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
|
|
436
440
|
expect(result.content[0].text).toContain('successfully deleted');
|
|
437
441
|
});
|
|
438
442
|
|
|
@@ -440,7 +444,7 @@ describe('mcp_server_improved - ghost_delete_page tool', () => {
|
|
|
440
444
|
mockDeletePage.mockRejectedValue(new Error('Page not found'));
|
|
441
445
|
|
|
442
446
|
const tool = mockTools.get('ghost_delete_page');
|
|
443
|
-
const result = await tool.handler({ id: '
|
|
447
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
444
448
|
|
|
445
449
|
expect(result.isError).toBe(true);
|
|
446
450
|
expect(result.content[0].text).toContain('Error deleting page');
|
|
@@ -462,9 +466,9 @@ describe('mcp_server_improved - ghost_search_pages tool', () => {
|
|
|
462
466
|
it('should have correct schema with query required', () => {
|
|
463
467
|
const tool = mockTools.get('ghost_search_pages');
|
|
464
468
|
expect(tool).toBeDefined();
|
|
465
|
-
expect(tool.schema.query).toBeDefined();
|
|
466
|
-
expect(tool.schema.status).toBeDefined();
|
|
467
|
-
expect(tool.schema.limit).toBeDefined();
|
|
469
|
+
expect(tool.schema.shape.query).toBeDefined();
|
|
470
|
+
expect(tool.schema.shape.status).toBeDefined();
|
|
471
|
+
expect(tool.schema.shape.limit).toBeDefined();
|
|
468
472
|
});
|
|
469
473
|
|
|
470
474
|
it('should search pages with query', async () => {
|
|
@@ -502,10 +506,10 @@ describe('mcp_server_improved - ghost_search_pages tool', () => {
|
|
|
502
506
|
const tool = mockTools.get('ghost_search_pages');
|
|
503
507
|
const schema = tool.schema;
|
|
504
508
|
|
|
505
|
-
expect(schema.limit).toBeDefined();
|
|
506
|
-
expect(() => schema.limit.parse(0)).toThrow();
|
|
507
|
-
expect(() => schema.limit.parse(51)).toThrow();
|
|
508
|
-
expect(schema.limit.parse(25)).toBe(25);
|
|
509
|
+
expect(schema.shape.limit).toBeDefined();
|
|
510
|
+
expect(() => schema.shape.limit.parse(0)).toThrow();
|
|
511
|
+
expect(() => schema.shape.limit.parse(51)).toThrow();
|
|
512
|
+
expect(schema.shape.limit.parse(25)).toBe(25);
|
|
509
513
|
});
|
|
510
514
|
|
|
511
515
|
it('should handle errors gracefully', async () => {
|
|
@@ -122,6 +122,76 @@ describe('Error Handling System', () => {
|
|
|
122
122
|
type: 'number.base',
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
it('should create validation error from Zod error', () => {
|
|
127
|
+
const zodError = {
|
|
128
|
+
errors: [
|
|
129
|
+
{
|
|
130
|
+
path: ['user', 'email'],
|
|
131
|
+
message: 'Invalid email',
|
|
132
|
+
code: 'invalid_string',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
path: ['age'],
|
|
136
|
+
message: 'Expected number, received string',
|
|
137
|
+
code: 'invalid_type',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const error = ValidationError.fromZod(zodError);
|
|
143
|
+
|
|
144
|
+
expect(error.message).toBe('Validation failed');
|
|
145
|
+
expect(error.errors).toHaveLength(2);
|
|
146
|
+
expect(error.errors[0]).toEqual({
|
|
147
|
+
field: 'user.email',
|
|
148
|
+
message: 'Invalid email',
|
|
149
|
+
type: 'invalid_string',
|
|
150
|
+
});
|
|
151
|
+
expect(error.errors[1]).toEqual({
|
|
152
|
+
field: 'age',
|
|
153
|
+
message: 'Expected number, received string',
|
|
154
|
+
type: 'invalid_type',
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should create validation error from Zod error with context', () => {
|
|
159
|
+
const zodError = {
|
|
160
|
+
errors: [
|
|
161
|
+
{
|
|
162
|
+
path: ['name'],
|
|
163
|
+
message: 'String must contain at least 1 character(s)',
|
|
164
|
+
code: 'too_small',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const error = ValidationError.fromZod(zodError, 'Tag creation');
|
|
170
|
+
|
|
171
|
+
expect(error.message).toBe('Tag creation: Validation failed');
|
|
172
|
+
expect(error.errors).toHaveLength(1);
|
|
173
|
+
expect(error.errors[0]).toEqual({
|
|
174
|
+
field: 'name',
|
|
175
|
+
message: 'String must contain at least 1 character(s)',
|
|
176
|
+
type: 'too_small',
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should create validation error from Zod error with empty path', () => {
|
|
181
|
+
const zodError = {
|
|
182
|
+
errors: [
|
|
183
|
+
{
|
|
184
|
+
path: [],
|
|
185
|
+
message: 'Invalid input',
|
|
186
|
+
code: 'custom',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const error = ValidationError.fromZod(zodError);
|
|
192
|
+
|
|
193
|
+
expect(error.errors[0].field).toBe('');
|
|
194
|
+
});
|
|
125
195
|
});
|
|
126
196
|
|
|
127
197
|
describe('AuthenticationError', () => {
|
package/src/errors/index.js
CHANGED
|
@@ -48,6 +48,16 @@ export class ValidationError extends BaseError {
|
|
|
48
48
|
}));
|
|
49
49
|
return new ValidationError('Validation failed', errors);
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
static fromZod(zodError, context = '') {
|
|
53
|
+
const errors = zodError.errors.map((err) => ({
|
|
54
|
+
field: err.path.join('.'),
|
|
55
|
+
message: err.message,
|
|
56
|
+
type: err.code,
|
|
57
|
+
}));
|
|
58
|
+
const message = context ? `${context}: Validation failed` : 'Validation failed';
|
|
59
|
+
return new ValidationError(message, errors);
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
package/src/mcp_server.js
CHANGED
|
@@ -14,6 +14,7 @@ import os from 'os';
|
|
|
14
14
|
import { v4 as uuidv4 } from 'uuid';
|
|
15
15
|
import { validateImageUrl, createSecureAxiosConfig } from './utils/urlValidator.js';
|
|
16
16
|
import { createContextLogger } from './utils/logger.js';
|
|
17
|
+
import { trackTempFile, cleanupTempFiles } from './utils/tempFileManager.js';
|
|
17
18
|
|
|
18
19
|
// Load environment variables (might be redundant if loaded elsewhere, but safe)
|
|
19
20
|
dotenv.config();
|
|
@@ -300,11 +301,17 @@ const uploadImageTool = new Tool({
|
|
|
300
301
|
writer.on('finish', resolve);
|
|
301
302
|
writer.on('error', reject);
|
|
302
303
|
});
|
|
304
|
+
// Track temp file for cleanup on process exit
|
|
305
|
+
trackTempFile(downloadedPath);
|
|
303
306
|
logger.fileOperation('download', downloadedPath);
|
|
304
307
|
|
|
305
308
|
// --- 3. Process the image (Optional) ---
|
|
306
309
|
// Using the service from subtask 4.2
|
|
307
310
|
processedPath = await processImage(downloadedPath, tempDir);
|
|
311
|
+
// Track processed file for cleanup on process exit
|
|
312
|
+
if (processedPath !== downloadedPath) {
|
|
313
|
+
trackTempFile(processedPath);
|
|
314
|
+
}
|
|
308
315
|
logger.fileOperation('process', processedPath);
|
|
309
316
|
|
|
310
317
|
// --- 4. Determine Alt Text ---
|
|
@@ -327,25 +334,8 @@ const uploadImageTool = new Tool({
|
|
|
327
334
|
// Add more specific error handling (download failed, processing failed, upload failed)
|
|
328
335
|
throw new Error(`Failed to upload image from URL ${imageUrl}: ${error.message}`);
|
|
329
336
|
} finally {
|
|
330
|
-
// --- 7. Cleanup temporary files ---
|
|
331
|
-
|
|
332
|
-
fs.unlink(downloadedPath, (err) => {
|
|
333
|
-
if (err)
|
|
334
|
-
logger.warn('Failed to delete temporary downloaded file', {
|
|
335
|
-
file: path.basename(downloadedPath),
|
|
336
|
-
error: err.message,
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
if (processedPath && processedPath !== downloadedPath) {
|
|
341
|
-
fs.unlink(processedPath, (err) => {
|
|
342
|
-
if (err)
|
|
343
|
-
logger.warn('Failed to delete temporary processed file', {
|
|
344
|
-
file: path.basename(processedPath),
|
|
345
|
-
error: err.message,
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
}
|
|
337
|
+
// --- 7. Cleanup temporary files with proper async/await ---
|
|
338
|
+
await cleanupTempFiles([downloadedPath, processedPath], logger);
|
|
349
339
|
}
|
|
350
340
|
},
|
|
351
341
|
});
|