hostinger-api-mcp 1.6.2 → 1.7.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 CHANGED
@@ -49,11 +49,11 @@ pnpm update -g hostinger-api-mcp
49
49
 
50
50
  This package installs the following MCP server commands:
51
51
 
52
- - `hostinger-api-mcp` — unified server with every tool (214 total)
53
- - `hostinger-agency-hosting-mcp` — 12 tools for agency-hosting
52
+ - `hostinger-api-mcp` — unified server with every tool (217 total)
53
+ - `hostinger-agency-hosting-mcp` — 14 tools for agency-hosting
54
54
  - `hostinger-billing-mcp` — 7 tools for billing
55
55
  - `hostinger-dns-mcp` — 8 tools for dns
56
- - `hostinger-domains-mcp` — 18 tools for domains
56
+ - `hostinger-domains-mcp` — 19 tools for domains
57
57
  - `hostinger-ecommerce-mcp` — 12 tools for ecommerce
58
58
  - `hostinger-horizons-mcp` — 2 tools for horizons
59
59
  - `hostinger-hosting-mcp` — 46 tools for hosting
@@ -203,6 +203,20 @@ This MCP server provides the following tools:
203
203
 
204
204
  ### `hostinger-agency-hosting-mcp`
205
205
 
206
+ #### agencyHosting_deployNodeStaticWebsite
207
+
208
+ Deploy a node-static Agency Plan (h5g) website from an archive file. WARNING: this overwrites the website's existing contents and cannot be undone — always confirm with the user before proceeding. Use this for Agency Plan websites of type node-static (a Node.js-built static site that requires a build step or a plain simple static site). The tool resolves the website from its domain, uploads the archive to the website's file browser over TUS, and triggers the build-assets process which builds the site and deploys the result to public_html. This operation is synchronous: the build and deployment complete before the tool returns, so the website is live as soon as the tool finishes successfully — there is no separate asynchronous build to wait for or poll. For plain PHP applications that should be extracted as-is, use agencyHosting_deployPhpApplication instead. The website UID is automatically resolved from the domain.
209
+
210
+ - **Method**: `custom`
211
+ - **Path**: `custom`
212
+
213
+ #### agencyHosting_deployPhpApplication
214
+
215
+ Deploy a PHP (or other non-build) Agency Plan (h5g) website from an archive file. WARNING: this overwrites the website's existing contents and cannot be undone — always confirm with the user before proceeding. Use this for Agency Plan websites where the archive contents should be extracted and served as-is with no build step (e.g., PHP applications). The tool resolves the website from its domain, uploads the archive to the website's file browser over TUS, and triggers the import-archive process which overwrites the website contents with the archive contents. This operation is synchronous: the archive is extracted and deployed before the tool returns, so the website is live as soon as the tool finishes successfully — there is no separate asynchronous build to wait for or poll. For node-static websites that require a build step, use agencyHosting_deployNodeStaticWebsite instead. The website UID is automatically resolved from the domain.
216
+
217
+ - **Method**: `custom`
218
+ - **Path**: `custom`
219
+
206
220
  #### agency-hosting_listAvailableDatacentersForAnAgencyPlanOrderV1
207
221
 
208
222
  Lists the datacenters available for provisioning a new website on the given Agency Plan
@@ -625,6 +639,16 @@ Use this endpoint to make domain owner's information publicly visible.
625
639
  - **Method**: `DELETE`
626
640
  - **Path**: `/api/domains/v1/portfolio/{domain}/privacy-protection`
627
641
 
642
+ #### domains_getDomainRenewalInformationV1
643
+
644
+ Retrieve renewal information for a specified domain, including its status and current
645
+ expiration date.
646
+
647
+ Use this endpoint to build renewal automation and expiry monitoring for a single domain.
648
+
649
+ - **Method**: `GET`
650
+ - **Path**: `/api/domains/v1/portfolio/{domain}/renewal`
651
+
628
652
  #### domains_updateDomainNameserversV1
629
653
 
630
654
  Set nameservers for a specified domain.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "description": "Hostinger API — manage domains, DNS, hosting, VPS, billing and more from Gemini CLI.",
5
5
  "mcpServers": {
6
6
  "hostinger": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hostinger-api-mcp",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "mcpName": "io.github.hostinger/hostinger-api-mcp",
5
5
  "description": "MCP server for Hostinger API",
6
6
  "repository": {
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/hostinger/api-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.6.2",
9
+ "version": "1.7.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "hostinger-api-mcp",
14
- "version": "1.6.2",
14
+ "version": "1.7.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -29,7 +29,7 @@ const SECURITY_SCHEMES = {
29
29
 
30
30
  /**
31
31
  * MCP Server for Hostinger API
32
- * Generated from OpenAPI spec version 1.7.0
32
+ * Generated from OpenAPI spec version 1.7.1
33
33
  */
34
34
  class MCPServer {
35
35
  constructor({ name, version, tools }) {
@@ -197,6 +197,10 @@ class MCPServer {
197
197
 
198
198
  async executeCustomTool(tool, params) {
199
199
  switch (tool.name) {
200
+ case 'agencyHosting_deployNodeStaticWebsite':
201
+ return await this.handleNodeStaticDeploy(params);
202
+ case 'agencyHosting_deployPhpApplication':
203
+ return await this.handlePhpAppDeploy(params);
200
204
  case 'hosting_importWordpressWebsite':
201
205
  return await this.handleWordpressWebsiteImport(params);
202
206
  case 'hosting_deployWordpressPlugin':
@@ -216,6 +220,533 @@ class MCPServer {
216
220
  }
217
221
  }
218
222
 
223
+ agencyHosting_normalizePath(pathString) {
224
+ return pathString.replace(/\\/g, '/');
225
+ }
226
+
227
+ agencyHosting_generateUploadDir() {
228
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
229
+ let result = '';
230
+ for (let i = 0; i < 12; i++) {
231
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
232
+ }
233
+ return result;
234
+ }
235
+
236
+ agencyHosting_validateRequiredParams(params) {
237
+ const { domain, archivePath, removeArchive } = params;
238
+
239
+ if (!domain || typeof domain !== 'string') {
240
+ throw new Error('domain is required and must be a string');
241
+ }
242
+
243
+ if (!archivePath || typeof archivePath !== 'string') {
244
+ throw new Error('archivePath is required and must be a string');
245
+ }
246
+
247
+ if (removeArchive !== undefined && typeof removeArchive !== 'boolean') {
248
+ throw new Error('removeArchive must be a boolean if provided');
249
+ }
250
+ }
251
+
252
+ agencyHosting_validateArchiveFile(archivePath) {
253
+ if (!fs.existsSync(archivePath)) {
254
+ throw new Error(`Archive file not found: ${archivePath}`);
255
+ }
256
+
257
+ const archiveStats = fs.statSync(archivePath);
258
+ if (!archiveStats.isFile()) {
259
+ throw new Error(`Archive path is not a file: ${archivePath}`);
260
+ }
261
+ }
262
+
263
+ agencyHosting_removeArchive(archivePath, removeArchive) {
264
+ if (!removeArchive) {
265
+ return false;
266
+ }
267
+
268
+ try {
269
+ this.log('info', `Removing archive file: ${archivePath}`);
270
+ fs.unlinkSync(archivePath);
271
+ this.log('info', `Successfully removed archive file: ${archivePath}`);
272
+ return true;
273
+ } catch (error) {
274
+ const errorMessage = error instanceof Error ? error.message : String(error);
275
+ this.log('error', `Failed to remove archive file: ${errorMessage}`);
276
+ return false;
277
+ }
278
+ }
279
+
280
+ async agencyHosting_resolveWebsiteUid(domain) {
281
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
282
+ const perPage = 100;
283
+
284
+ try {
285
+ const bearerToken = await this.getAuthToken();
286
+ let page = 1;
287
+ let total = 0;
288
+
289
+ do {
290
+ const url = new URL(
291
+ `api/agency-hosting/v1/domains?page=${page}&per_page=${perPage}`,
292
+ baseUrl
293
+ ).toString();
294
+
295
+ const config = {
296
+ method: 'get',
297
+ url,
298
+ headers: {
299
+ ...this.headers,
300
+ 'Authorization': `Bearer ${bearerToken}`
301
+ },
302
+ timeout: 60000, // 60s
303
+ validateStatus: function (status) {
304
+ return status < 500;
305
+ }
306
+ };
307
+
308
+ const response = await axios(config);
309
+
310
+ if (response.status !== 200) {
311
+ throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
312
+ }
313
+
314
+ const domains = response.data?.data ?? [];
315
+ const match = domains.find((item) => item.fqdn === domain);
316
+ if (match?.website_uid) {
317
+ this.log('info', `Resolved website UID: ${match.website_uid} for domain: ${domain}`);
318
+ return match.website_uid;
319
+ }
320
+
321
+ total = response.data?.meta?.total ?? domains.length;
322
+ page++;
323
+ } while ((page - 1) * perPage < total);
324
+
325
+ throw new Error(`No Agency Plan website found for domain: ${domain}`);
326
+
327
+ } catch (error) {
328
+ const errorMessage = error instanceof Error ? error.message : String(error);
329
+ this.log('error', `Failed to resolve website UID for domain ${domain}: ${errorMessage}`);
330
+
331
+ if (axios.isAxiosError(error)) {
332
+ const responseData = error.response?.data;
333
+ const responseStatus = error.response?.status;
334
+ this.log('error', 'API Error Details:', {
335
+ status: responseStatus,
336
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
337
+ });
338
+ }
339
+
340
+ throw error;
341
+ }
342
+ }
343
+
344
+ async agencyHosting_fetchUploadCredentials(websiteUid) {
345
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
346
+ const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/files/upload-urls`, baseUrl).toString();
347
+
348
+ try {
349
+ const bearerToken = await this.getAuthToken();
350
+
351
+ const config = {
352
+ method: 'post',
353
+ url,
354
+ headers: {
355
+ ...this.headers,
356
+ 'Authorization': `Bearer ${bearerToken}`,
357
+ 'Content-Type': 'application/json'
358
+ },
359
+ timeout: 60000, // 60s
360
+ validateStatus: function (status) {
361
+ return status < 500;
362
+ }
363
+ };
364
+
365
+ const response = await axios(config);
366
+
367
+ if (response.status !== 200) {
368
+ throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
369
+ }
370
+
371
+ return response.data;
372
+
373
+ } catch (error) {
374
+ const errorMessage = error instanceof Error ? error.message : String(error);
375
+ this.log('error', `Failed to fetch upload credentials: ${errorMessage}`);
376
+
377
+ if (axios.isAxiosError(error)) {
378
+ const responseData = error.response?.data;
379
+ const responseStatus = error.response?.status;
380
+ this.log('error', 'API Error Details:', {
381
+ status: responseStatus,
382
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
383
+ });
384
+ }
385
+
386
+ throw error;
387
+ }
388
+ }
389
+
390
+ async agencyHosting_uploadFile(filePath, relativePath, uploadUrl, authRestToken, authToken) {
391
+ return new Promise(async (resolve, reject) => {
392
+ try {
393
+ const stats = fs.statSync(filePath);
394
+ const fileStream = fs.createReadStream(filePath);
395
+
396
+ const cleanUploadUrl = uploadUrl.replace(new RegExp('/$'), '');
397
+ const normalizedPath = this.agencyHosting_normalizePath(relativePath);
398
+ const uploadUrlWithFile = `${cleanUploadUrl}/${normalizedPath}?override=true`;
399
+
400
+ const requestHeaders = {
401
+ 'X-Auth': authToken,
402
+ 'X-Auth-Rest': authRestToken,
403
+ 'upload-length': stats.size.toString(),
404
+ 'upload-offset': '0'
405
+ };
406
+
407
+ try {
408
+ this.log('debug', `Making pre-upload POST request to ${uploadUrlWithFile}`);
409
+ await axios.post(uploadUrlWithFile, '', {
410
+ headers: requestHeaders,
411
+ timeout: 60000, // 60s
412
+ validateStatus: function (status) {
413
+ return status == 201;
414
+ }
415
+ });
416
+ } catch (error) {
417
+ const errorMessage = error instanceof Error ? error.message : String(error);
418
+
419
+ if (axios.isAxiosError(error)) {
420
+ const responseData = error.response?.data;
421
+ const responseStatus = error.response?.status;
422
+ const responseHeaders = error.response?.headers;
423
+ const responseText = typeof responseData === 'object' ? JSON.stringify(responseData) : responseData;
424
+
425
+ this.log('error', 'Pre-upload POST request failed - Full Response Details:', {
426
+ status: responseStatus,
427
+ headers: responseHeaders,
428
+ data: responseText,
429
+ message: errorMessage
430
+ });
431
+ reject(new Error(`Pre-upload request failed: ${errorMessage}`));
432
+ return;
433
+ } else {
434
+ this.log('error', `Pre-upload POST request failed: ${errorMessage}`);
435
+ reject(new Error(`Pre-upload request failed: ${errorMessage}`));
436
+ return;
437
+ }
438
+ }
439
+
440
+ const upload = new tus.Upload(fileStream, {
441
+ uploadUrl: uploadUrlWithFile,
442
+ retryDelays: [1000, 2000, 4000, 8000, 16000, 20000],
443
+ uploadDataDuringCreation: false,
444
+ parallelUploads: 1,
445
+ chunkSize: 10485760,
446
+ headers: requestHeaders,
447
+ removeFingerprintOnSuccess: true,
448
+ uploadSize: stats.size,
449
+ metadata: {
450
+ filename: path.basename(relativePath)
451
+ },
452
+ onError: (error) => {
453
+ this.log('error', `TUS upload error for ${relativePath}`, { error: error.message });
454
+ reject(new Error(`Upload failed: ${error.message}`));
455
+ },
456
+ onSuccess: () => {
457
+ this.log('info', `TUS upload completed for ${relativePath}`, { url: upload.url });
458
+ resolve({
459
+ url: upload.url || uploadUrlWithFile,
460
+ filename: relativePath
461
+ });
462
+ }
463
+ });
464
+
465
+ upload.start();
466
+
467
+ } catch (error) {
468
+ const errorMessage = error instanceof Error ? error.message : String(error);
469
+ this.log('error', `Error preparing upload for ${filePath}`, { error: errorMessage });
470
+ reject(new Error(`Failed to prepare upload: ${errorMessage}`));
471
+ }
472
+ });
473
+ }
474
+
475
+ async agencyHosting_deployNodeStatic_triggerBuildAssets(websiteUid, archivePath) {
476
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
477
+ const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/build-assets`, baseUrl).toString();
478
+
479
+ try {
480
+ const bearerToken = await this.getAuthToken();
481
+
482
+ const config = {
483
+ method: 'post',
484
+ url,
485
+ headers: {
486
+ ...this.headers,
487
+ 'Authorization': `Bearer ${bearerToken}`,
488
+ 'Content-Type': 'application/json'
489
+ },
490
+ data: {
491
+ archive_path: archivePath
492
+ },
493
+ timeout: 60000,
494
+ validateStatus: function (status) {
495
+ return status < 500;
496
+ }
497
+ };
498
+
499
+ const response = await axios(config);
500
+
501
+ if (response.status < 200 || response.status >= 300) {
502
+ throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
503
+ }
504
+
505
+ this.log('info', `Successfully triggered build for website ${websiteUid}`);
506
+ return response.data;
507
+
508
+ } catch (error) {
509
+ const errorMessage = error instanceof Error ? error.message : String(error);
510
+ this.log('error', `Failed to trigger build: ${errorMessage}`);
511
+
512
+ if (axios.isAxiosError(error)) {
513
+ const responseData = error.response?.data;
514
+ const responseStatus = error.response?.status;
515
+ this.log('error', 'API Error Details:', {
516
+ status: responseStatus,
517
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
518
+ });
519
+ }
520
+
521
+ throw error;
522
+ }
523
+ }
524
+
525
+ async handleNodeStaticDeploy(params) {
526
+ const { domain, archivePath, removeArchive = true } = params;
527
+
528
+ this.agencyHosting_validateRequiredParams(params);
529
+ this.agencyHosting_validateArchiveFile(archivePath);
530
+
531
+ this.log('info', `Resolving website UID from domain: ${domain}`);
532
+ const websiteUid = await this.agencyHosting_resolveWebsiteUid(domain);
533
+
534
+ this.log('info', `Starting archive upload for ${domain}`);
535
+
536
+ let uploadCredentials;
537
+ try {
538
+ uploadCredentials = await this.agencyHosting_fetchUploadCredentials(websiteUid);
539
+ } catch (error) {
540
+ const errorMessage = error instanceof Error ? error.message : String(error);
541
+ throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
542
+ }
543
+
544
+ const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
545
+
546
+ if (!uploadUrl || !authToken || !authRestToken) {
547
+ throw new Error('Invalid upload credentials received from API');
548
+ }
549
+
550
+ const archiveBasename = path.basename(archivePath);
551
+ const uploadDir = `.h5g/${this.agencyHosting_generateUploadDir()}`;
552
+ let uploadResult;
553
+ try {
554
+ uploadResult = await this.agencyHosting_uploadFile(
555
+ archivePath,
556
+ `${uploadDir}/${archiveBasename}`,
557
+ uploadUrl,
558
+ authRestToken,
559
+ authToken
560
+ );
561
+
562
+ this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
563
+ } catch (error) {
564
+ const errorMessage = error instanceof Error ? error.message : String(error);
565
+ throw new Error(`Failed to upload archive: ${errorMessage}`);
566
+ }
567
+
568
+ let deployResult;
569
+ try {
570
+ this.log('info', `Triggering build for ${domain}`);
571
+ deployResult = await this.agencyHosting_deployNodeStatic_triggerBuildAssets(websiteUid, uploadDir);
572
+ } catch (error) {
573
+ const errorMessage = error instanceof Error ? error.message : String(error);
574
+ this.log('error', `Failed to trigger build: ${errorMessage}`);
575
+ const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
576
+
577
+ return {
578
+ upload: {
579
+ status: 'success',
580
+ data: {
581
+ filename: uploadResult.filename
582
+ }
583
+ },
584
+ deploy: {
585
+ status: 'error',
586
+ error: errorMessage
587
+ },
588
+ removeArchive: {
589
+ status: archiveRemoved ? 'success' : 'skipped'
590
+ }
591
+ };
592
+ }
593
+
594
+ const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
595
+
596
+ return {
597
+ upload: {
598
+ status: 'success',
599
+ data: {
600
+ filename: uploadResult.filename
601
+ }
602
+ },
603
+ deploy: {
604
+ status: 'success',
605
+ data: deployResult
606
+ },
607
+ removeArchive: {
608
+ status: archiveRemoved ? 'success' : 'skipped'
609
+ }
610
+ };
611
+ }
612
+
613
+ async agencyHosting_deployPhpApp_triggerImportArchive(websiteUid, archiveName) {
614
+ const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`;
615
+ const url = new URL(`api/agency-hosting/v1/websites/${websiteUid}/files/import-archive`, baseUrl).toString();
616
+
617
+ try {
618
+ const bearerToken = await this.getAuthToken();
619
+
620
+ const config = {
621
+ method: 'post',
622
+ url,
623
+ headers: {
624
+ ...this.headers,
625
+ 'Authorization': `Bearer ${bearerToken}`,
626
+ 'Content-Type': 'application/json'
627
+ },
628
+ data: {
629
+ archive_name: archiveName
630
+ },
631
+ timeout: 60000,
632
+ validateStatus: function (status) {
633
+ return status < 500;
634
+ }
635
+ };
636
+
637
+ const response = await axios(config);
638
+
639
+ if (response.status < 200 || response.status >= 300) {
640
+ throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`);
641
+ }
642
+
643
+ this.log('info', `Successfully triggered import for website ${websiteUid}`);
644
+ return response.data;
645
+
646
+ } catch (error) {
647
+ const errorMessage = error instanceof Error ? error.message : String(error);
648
+ this.log('error', `Failed to trigger import: ${errorMessage}`);
649
+
650
+ if (axios.isAxiosError(error)) {
651
+ const responseData = error.response?.data;
652
+ const responseStatus = error.response?.status;
653
+ this.log('error', 'API Error Details:', {
654
+ status: responseStatus,
655
+ data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData
656
+ });
657
+ }
658
+
659
+ throw error;
660
+ }
661
+ }
662
+
663
+ async handlePhpAppDeploy(params) {
664
+ const { domain, archivePath, removeArchive = true } = params;
665
+
666
+ this.agencyHosting_validateRequiredParams(params);
667
+ this.agencyHosting_validateArchiveFile(archivePath);
668
+
669
+ this.log('info', `Resolving website UID from domain: ${domain}`);
670
+ const websiteUid = await this.agencyHosting_resolveWebsiteUid(domain);
671
+
672
+ this.log('info', `Starting archive upload for ${domain}`);
673
+
674
+ let uploadCredentials;
675
+ try {
676
+ uploadCredentials = await this.agencyHosting_fetchUploadCredentials(websiteUid);
677
+ } catch (error) {
678
+ const errorMessage = error instanceof Error ? error.message : String(error);
679
+ throw new Error(`Failed to fetch upload credentials: ${errorMessage}`);
680
+ }
681
+
682
+ const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials;
683
+
684
+ if (!uploadUrl || !authToken || !authRestToken) {
685
+ throw new Error('Invalid upload credentials received from API');
686
+ }
687
+
688
+ const archiveBasename = path.basename(archivePath);
689
+ let uploadResult;
690
+ try {
691
+ uploadResult = await this.agencyHosting_uploadFile(
692
+ archivePath,
693
+ `.h5g/${archiveBasename}`,
694
+ uploadUrl,
695
+ authRestToken,
696
+ authToken
697
+ );
698
+
699
+ this.log('info', `Successfully uploaded archive: ${archiveBasename}`);
700
+ } catch (error) {
701
+ const errorMessage = error instanceof Error ? error.message : String(error);
702
+ throw new Error(`Failed to upload archive: ${errorMessage}`);
703
+ }
704
+
705
+ let deployResult;
706
+ try {
707
+ this.log('info', `Triggering import for ${domain}`);
708
+ deployResult = await this.agencyHosting_deployPhpApp_triggerImportArchive(websiteUid, archiveBasename);
709
+ } catch (error) {
710
+ const errorMessage = error instanceof Error ? error.message : String(error);
711
+ this.log('error', `Failed to trigger import: ${errorMessage}`);
712
+ const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
713
+
714
+ return {
715
+ upload: {
716
+ status: 'success',
717
+ data: {
718
+ filename: uploadResult.filename
719
+ }
720
+ },
721
+ deploy: {
722
+ status: 'error',
723
+ error: errorMessage
724
+ },
725
+ removeArchive: {
726
+ status: archiveRemoved ? 'success' : 'skipped'
727
+ }
728
+ };
729
+ }
730
+
731
+ const archiveRemoved = this.agencyHosting_removeArchive(archivePath, removeArchive);
732
+
733
+ return {
734
+ upload: {
735
+ status: 'success',
736
+ data: {
737
+ filename: uploadResult.filename
738
+ }
739
+ },
740
+ deploy: {
741
+ status: 'success',
742
+ data: deployResult
743
+ },
744
+ removeArchive: {
745
+ status: archiveRemoved ? 'success' : 'skipped'
746
+ }
747
+ };
748
+ }
749
+
219
750
  normalizePath(pathString) {
220
751
  return pathString.replace(/\\/g, '/');
221
752
  }