hostinger-api-mcp 3.24.2 → 3.25.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/CHANGELOG.md +28 -0
- package/README.md +24 -98
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/connect-domain/SKILL.md +4 -4
- package/skills/deploy-to-hosting/SKILL.md +3 -3
- package/skills/headless/SKILL.md +3 -3
- package/skills/headless/VERSION +2 -2
- package/skills/headless/steps/01-check-state/SKILL.md +1 -1
- package/skills/headless/steps/03-provision/SKILL.md +8 -8
- package/skills/headless/steps/03-provision/ecommerce/SKILL.md +4 -5
- package/skills/headless/steps/03-provision/webapp/SKILL.md +6 -7
- package/skills/headless/steps/03-provision/wordpress/SKILL.md +8 -9
- package/src/core/runtime.js +156 -2
- package/src/core/runtime.ts +156 -2
- package/src/core/skills.js +24 -24
- package/src/core/skills.ts +24 -24
- package/src/core/tools/all.js +189 -10
- package/src/core/tools/all.ts +189 -10
- package/src/core/tools/hosting.js +189 -10
- package/src/core/tools/hosting.ts +189 -10
- package/src/servers/agency-hosting.js +1 -1
- package/src/servers/agency-hosting.ts +1 -1
- package/src/servers/agent-readiness.js +1 -1
- package/src/servers/agent-readiness.ts +1 -1
- package/src/servers/all.js +1 -1
- package/src/servers/all.ts +1 -1
- package/src/servers/billing.js +1 -1
- package/src/servers/billing.ts +1 -1
- package/src/servers/dns.js +1 -1
- package/src/servers/dns.ts +1 -1
- package/src/servers/domains.js +1 -1
- package/src/servers/domains.ts +1 -1
- package/src/servers/ecommerce.js +1 -1
- package/src/servers/ecommerce.ts +1 -1
- package/src/servers/horizons.js +1 -1
- package/src/servers/horizons.ts +1 -1
- package/src/servers/hosting.js +1 -1
- package/src/servers/hosting.ts +1 -1
- package/src/servers/mail.js +1 -1
- package/src/servers/mail.ts +1 -1
- package/src/servers/reach.js +1 -1
- package/src/servers/reach.ts +1 -1
- package/src/servers/vps.js +1 -1
- package/src/servers/vps.ts +1 -1
- package/src/servers/wordpress.js +1 -1
- package/src/servers/wordpress.ts +1 -1
- package/types.d.ts +90 -2
package/src/core/runtime.js
CHANGED
|
@@ -304,6 +304,14 @@ class MCPServer {
|
|
|
304
304
|
return await this.handleHostingWebsitesUpdateSettings(params);
|
|
305
305
|
case 'hosting_cache_clear':
|
|
306
306
|
return await this.handleHostingCacheClear(params);
|
|
307
|
+
case 'hosting_websites_list-domains':
|
|
308
|
+
return await this.handleHostingWebsitesListDomains(params);
|
|
309
|
+
case 'hosting_websites_add-domain':
|
|
310
|
+
return await this.handleHostingWebsitesAddDomain(params);
|
|
311
|
+
case 'hosting_websites_remove-domain':
|
|
312
|
+
return await this.handleHostingWebsitesRemoveDomain(params);
|
|
313
|
+
case 'hosting_websites_start-setup':
|
|
314
|
+
return await this.handleHostingWebsitesStartSetup(params);
|
|
307
315
|
case 'hosting_import-wordpress-website':
|
|
308
316
|
return await this.handleWordpressWebsiteImport(params);
|
|
309
317
|
case 'hosting_deploy-wordpress-plugin':
|
|
@@ -385,9 +393,10 @@ class MCPServer {
|
|
|
385
393
|
const homes = path.dirname(home);
|
|
386
394
|
const otherHome = !inHome && path.dirname(homes) !== homes && inside(homes, real);
|
|
387
395
|
// Hidden directories at the top of the home (dotfiles, tool state) and the per-user app data (Library,
|
|
388
|
-
// AppData
|
|
396
|
+
// AppData, and ~/snap where Ubuntu's snap packages such as Firefox and Chromium keep their profiles) hold keychains,
|
|
397
|
+
// browser profiles and tokens; .claude-worktrees holds Claude Code's git worktrees.
|
|
389
398
|
const top = path.relative(fold(home), fold(real)).split(path.sep)[0];
|
|
390
|
-
const inHomeData = inHome && top !== '.claude-worktrees' && (top.startsWith('.') || top === fold('Library') || top === fold('AppData'));
|
|
399
|
+
const inHomeData = inHome && top !== '.claude-worktrees' && (top.startsWith('.') || top === fold('Library') || top === fold('AppData') || top === 'snap');
|
|
391
400
|
const inSystem = !inHome && this.systemDirectories.some((dir) => inside(dir, real));
|
|
392
401
|
// Windows drops trailing dots and spaces from a name: .ssh. is .ssh.
|
|
393
402
|
const inCredentials = real.split(path.sep).some((segment) => this.credentialDirectories.includes((process.platform === 'win32' ? segment.replace(/[. ]+$/, '') : segment).toLowerCase()));
|
|
@@ -676,6 +685,14 @@ class MCPServer {
|
|
|
676
685
|
throw new Error('No website found for domain: ' + domain);
|
|
677
686
|
}
|
|
678
687
|
|
|
688
|
+
// The items of a parked domain or subdomain list: the API answers with a bare array.
|
|
689
|
+
domainItems(response) {
|
|
690
|
+
if (Array.isArray(response)) {
|
|
691
|
+
return response;
|
|
692
|
+
}
|
|
693
|
+
return response && Array.isArray(response.data) ? response.data : [];
|
|
694
|
+
}
|
|
695
|
+
|
|
679
696
|
hosting_deploy_types = ['static', 'nodejs', 'wordpress-site', 'wordpress-plugin', 'wordpress-theme'];
|
|
680
697
|
|
|
681
698
|
// A package.json that depends on one of these is an app with a server, not dev tooling.
|
|
@@ -1547,6 +1564,143 @@ class MCPServer {
|
|
|
1547
1564
|
return { domain, cleared: scope, directory, response };
|
|
1548
1565
|
}
|
|
1549
1566
|
|
|
1567
|
+
// A 404 means the domain is not a website on the account, which fails the whole call; any other
|
|
1568
|
+
// failure only fails its own list.
|
|
1569
|
+
async hosting_websitesListDomains_section(request, pick) {
|
|
1570
|
+
try {
|
|
1571
|
+
return this.domainItems(await request).map(pick);
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
if (error instanceof ApiExecutionError && error.status === 404) {
|
|
1574
|
+
throw error;
|
|
1575
|
+
}
|
|
1576
|
+
return { error: this.formatToolError(error) };
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
async handleHostingWebsitesListDomains(params) {
|
|
1581
|
+
const { domain: requestedDomain } = params;
|
|
1582
|
+
|
|
1583
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1584
|
+
throw new Error('domain is required and must be a string');
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1588
|
+
const target = { username, domain };
|
|
1589
|
+
|
|
1590
|
+
const [parked, subdomains] = await Promise.all([
|
|
1591
|
+
this.hosting_websitesListDomains_section(this.callOperation('hosting_domains_list-website-parked', target), (item) => ({
|
|
1592
|
+
domain: item.domain,
|
|
1593
|
+
type: item.type,
|
|
1594
|
+
root_directory: item.root_directory,
|
|
1595
|
+
})),
|
|
1596
|
+
this.hosting_websitesListDomains_section(this.callOperation('hosting_domains_list-website-subdomains', target), (item) => ({
|
|
1597
|
+
domain: item.domain,
|
|
1598
|
+
subdomain: item.subdomain,
|
|
1599
|
+
root_directory: item.root_directory,
|
|
1600
|
+
})),
|
|
1601
|
+
]);
|
|
1602
|
+
|
|
1603
|
+
return { domain, parked, subdomains };
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
async handleHostingWebsitesAddDomain(params) {
|
|
1607
|
+
const { domain: requestedDomain, type, name } = params;
|
|
1608
|
+
// Models send null for fields they leave unset.
|
|
1609
|
+
const directory = params.directory ?? null;
|
|
1610
|
+
const usePublicDirectory = params.usePublicDirectory ?? null;
|
|
1611
|
+
|
|
1612
|
+
if (type !== 'parked' && type !== 'subdomain') {
|
|
1613
|
+
throw new Error('type must be parked or subdomain');
|
|
1614
|
+
}
|
|
1615
|
+
if (!name || typeof name !== 'string') {
|
|
1616
|
+
throw new Error('name is required and must be a string');
|
|
1617
|
+
}
|
|
1618
|
+
if (directory !== null && (typeof directory !== 'string' || directory === '')) {
|
|
1619
|
+
throw new Error('directory must be a non-empty string when provided');
|
|
1620
|
+
}
|
|
1621
|
+
if (usePublicDirectory !== null && typeof usePublicDirectory !== 'boolean') {
|
|
1622
|
+
throw new Error('usePublicDirectory must be a boolean');
|
|
1623
|
+
}
|
|
1624
|
+
if (type === 'parked' && (directory !== null || usePublicDirectory !== null)) {
|
|
1625
|
+
throw new Error('directory and usePublicDirectory apply only to type subdomain');
|
|
1626
|
+
}
|
|
1627
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1628
|
+
throw new Error('domain is required and must be a string');
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
let prefix = '';
|
|
1632
|
+
if (type === 'subdomain') {
|
|
1633
|
+
const suffix = '.' + requestedDomain.toLowerCase();
|
|
1634
|
+
prefix = name.toLowerCase().endsWith(suffix) ? name.slice(0, -suffix.length) : name;
|
|
1635
|
+
if (prefix === '' || (prefix === name && name.includes('.'))) {
|
|
1636
|
+
throw new Error('name must be a subdomain prefix (blog) or a name under ' + requestedDomain + ' (blog.' + requestedDomain + ')');
|
|
1637
|
+
}
|
|
1638
|
+
if (prefix.toLowerCase() === 'www') {
|
|
1639
|
+
throw new Error('www cannot be added as a subdomain: it already belongs to ' + requestedDomain);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1644
|
+
|
|
1645
|
+
if (type === 'parked') {
|
|
1646
|
+
const response = await this.callOperation('hosting_domains_create-website-parked', { username, domain, parked_domain: name });
|
|
1647
|
+
return { domain, type, name, response };
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const response = await this.callOperation('hosting_domains_create-website-subdomain', {
|
|
1651
|
+
username,
|
|
1652
|
+
domain,
|
|
1653
|
+
subdomain: prefix,
|
|
1654
|
+
...(directory !== null ? { directory } : {}),
|
|
1655
|
+
...(usePublicDirectory !== null ? { is_using_public_directory: usePublicDirectory } : {}),
|
|
1656
|
+
});
|
|
1657
|
+
return { domain, type, name: prefix + '.' + domain, response };
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
async handleHostingWebsitesRemoveDomain(params) {
|
|
1661
|
+
const { domain: requestedDomain, name } = params;
|
|
1662
|
+
|
|
1663
|
+
if (!name || typeof name !== 'string') {
|
|
1664
|
+
throw new Error('name is required and must be a string');
|
|
1665
|
+
}
|
|
1666
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1667
|
+
throw new Error('domain is required and must be a string');
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1671
|
+
const target = { username, domain };
|
|
1672
|
+
|
|
1673
|
+
const [parkedResponse, subdomainsResponse] = await Promise.all([
|
|
1674
|
+
this.callOperation('hosting_domains_list-website-parked', target),
|
|
1675
|
+
this.callOperation('hosting_domains_list-website-subdomains', target),
|
|
1676
|
+
]);
|
|
1677
|
+
const parkedDomains = this.domainItems(parkedResponse);
|
|
1678
|
+
const subdomains = this.domainItems(subdomainsResponse);
|
|
1679
|
+
const wanted = name.toLowerCase();
|
|
1680
|
+
|
|
1681
|
+
const parked = parkedDomains.find((item) => typeof item.domain === 'string' && item.domain.toLowerCase() === wanted);
|
|
1682
|
+
if (parked) {
|
|
1683
|
+
const response = await this.callOperation('hosting_domains_delete-website-parked', { ...target, parkedDomain: parked.domain });
|
|
1684
|
+
return { domain, type: 'parked', name: parked.domain, response };
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const subdomain = subdomains.find((item) => (typeof item.domain === 'string' && item.domain.toLowerCase() === wanted)
|
|
1688
|
+
|| (typeof item.subdomain === 'string' && item.subdomain.toLowerCase() === wanted));
|
|
1689
|
+
if (subdomain) {
|
|
1690
|
+
const response = await this.callOperation('hosting_domains_delete-website-subdomain', { ...target, subdomain: subdomain.subdomain });
|
|
1691
|
+
return { domain, type: 'subdomain', name: subdomain.domain, response };
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const listed = (items) => items.map((item) => item.domain).filter(Boolean).join(', ') || 'none';
|
|
1695
|
+
throw new Error(name + ' is not a parked domain or subdomain of ' + domain + '. Parked domains: ' + listed(parkedDomains) + '. Subdomains: ' + listed(subdomains) + '.');
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
async handleHostingWebsitesStartSetup(params) {
|
|
1699
|
+
// Models send null for fields they leave unset; the setup endpoint rejects some of them.
|
|
1700
|
+
const forwarded = Object.fromEntries(Object.entries(params).filter(([, value]) => value !== null && value !== undefined));
|
|
1701
|
+
return this.callOperation('hosting_websites_create', forwarded);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1550
1704
|
hosting_importWordpressWebsite_validateArchiveFormat(filePath) {
|
|
1551
1705
|
const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
|
|
1552
1706
|
const fileName = path.basename(filePath).toLowerCase();
|
package/src/core/runtime.ts
CHANGED
|
@@ -336,6 +336,14 @@ class MCPServer {
|
|
|
336
336
|
return await this.handleHostingWebsitesUpdateSettings(params);
|
|
337
337
|
case 'hosting_cache_clear':
|
|
338
338
|
return await this.handleHostingCacheClear(params);
|
|
339
|
+
case 'hosting_websites_list-domains':
|
|
340
|
+
return await this.handleHostingWebsitesListDomains(params);
|
|
341
|
+
case 'hosting_websites_add-domain':
|
|
342
|
+
return await this.handleHostingWebsitesAddDomain(params);
|
|
343
|
+
case 'hosting_websites_remove-domain':
|
|
344
|
+
return await this.handleHostingWebsitesRemoveDomain(params);
|
|
345
|
+
case 'hosting_websites_start-setup':
|
|
346
|
+
return await this.handleHostingWebsitesStartSetup(params);
|
|
339
347
|
case 'hosting_import-wordpress-website':
|
|
340
348
|
return await this.handleWordpressWebsiteImport(params);
|
|
341
349
|
case 'hosting_deploy-wordpress-plugin':
|
|
@@ -417,9 +425,10 @@ class MCPServer {
|
|
|
417
425
|
const homes = path.dirname(home);
|
|
418
426
|
const otherHome = !inHome && path.dirname(homes) !== homes && inside(homes, real);
|
|
419
427
|
// Hidden directories at the top of the home (dotfiles, tool state) and the per-user app data (Library,
|
|
420
|
-
// AppData
|
|
428
|
+
// AppData, and ~/snap where Ubuntu's snap packages such as Firefox and Chromium keep their profiles) hold keychains,
|
|
429
|
+
// browser profiles and tokens; .claude-worktrees holds Claude Code's git worktrees.
|
|
421
430
|
const top = path.relative(fold(home), fold(real)).split(path.sep)[0];
|
|
422
|
-
const inHomeData = inHome && top !== '.claude-worktrees' && (top.startsWith('.') || top === fold('Library') || top === fold('AppData'));
|
|
431
|
+
const inHomeData = inHome && top !== '.claude-worktrees' && (top.startsWith('.') || top === fold('Library') || top === fold('AppData') || top === 'snap');
|
|
423
432
|
const inSystem = !inHome && this.systemDirectories.some((dir: string) => inside(dir, real));
|
|
424
433
|
// Windows drops trailing dots and spaces from a name: .ssh. is .ssh.
|
|
425
434
|
const inCredentials = real.split(path.sep).some((segment: string) => this.credentialDirectories.includes((process.platform === 'win32' ? segment.replace(/[. ]+$/, '') : segment).toLowerCase()));
|
|
@@ -714,6 +723,14 @@ class MCPServer {
|
|
|
714
723
|
throw new Error('No website found for domain: ' + domain);
|
|
715
724
|
}
|
|
716
725
|
|
|
726
|
+
// The items of a parked domain or subdomain list: the API answers with a bare array.
|
|
727
|
+
private domainItems(response: any): any[] {
|
|
728
|
+
if (Array.isArray(response)) {
|
|
729
|
+
return response;
|
|
730
|
+
}
|
|
731
|
+
return response && Array.isArray(response.data) ? response.data : [];
|
|
732
|
+
}
|
|
733
|
+
|
|
717
734
|
private hosting_deploy_types = ['static', 'nodejs', 'wordpress-site', 'wordpress-plugin', 'wordpress-theme'];
|
|
718
735
|
|
|
719
736
|
// A package.json that depends on one of these is an app with a server, not dev tooling.
|
|
@@ -1585,6 +1602,143 @@ class MCPServer {
|
|
|
1585
1602
|
return { domain, cleared: scope, directory, response };
|
|
1586
1603
|
}
|
|
1587
1604
|
|
|
1605
|
+
// A 404 means the domain is not a website on the account, which fails the whole call; any other
|
|
1606
|
+
// failure only fails its own list.
|
|
1607
|
+
private async hosting_websitesListDomains_section(request: Promise<any>, pick: (item: any) => any): Promise<any> {
|
|
1608
|
+
try {
|
|
1609
|
+
return this.domainItems(await request).map(pick);
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
if (error instanceof ApiExecutionError && error.status === 404) {
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
return { error: this.formatToolError(error) };
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
private async handleHostingWebsitesListDomains(params: Record<string, any>): Promise<any> {
|
|
1619
|
+
const { domain: requestedDomain } = params;
|
|
1620
|
+
|
|
1621
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1622
|
+
throw new Error('domain is required and must be a string');
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1626
|
+
const target = { username, domain };
|
|
1627
|
+
|
|
1628
|
+
const [parked, subdomains] = await Promise.all([
|
|
1629
|
+
this.hosting_websitesListDomains_section(this.callOperation('hosting_domains_list-website-parked', target), (item: any) => ({
|
|
1630
|
+
domain: item.domain,
|
|
1631
|
+
type: item.type,
|
|
1632
|
+
root_directory: item.root_directory,
|
|
1633
|
+
})),
|
|
1634
|
+
this.hosting_websitesListDomains_section(this.callOperation('hosting_domains_list-website-subdomains', target), (item: any) => ({
|
|
1635
|
+
domain: item.domain,
|
|
1636
|
+
subdomain: item.subdomain,
|
|
1637
|
+
root_directory: item.root_directory,
|
|
1638
|
+
})),
|
|
1639
|
+
]);
|
|
1640
|
+
|
|
1641
|
+
return { domain, parked, subdomains };
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
private async handleHostingWebsitesAddDomain(params: Record<string, any>): Promise<any> {
|
|
1645
|
+
const { domain: requestedDomain, type, name } = params;
|
|
1646
|
+
// Models send null for fields they leave unset.
|
|
1647
|
+
const directory = params.directory ?? null;
|
|
1648
|
+
const usePublicDirectory = params.usePublicDirectory ?? null;
|
|
1649
|
+
|
|
1650
|
+
if (type !== 'parked' && type !== 'subdomain') {
|
|
1651
|
+
throw new Error('type must be parked or subdomain');
|
|
1652
|
+
}
|
|
1653
|
+
if (!name || typeof name !== 'string') {
|
|
1654
|
+
throw new Error('name is required and must be a string');
|
|
1655
|
+
}
|
|
1656
|
+
if (directory !== null && (typeof directory !== 'string' || directory === '')) {
|
|
1657
|
+
throw new Error('directory must be a non-empty string when provided');
|
|
1658
|
+
}
|
|
1659
|
+
if (usePublicDirectory !== null && typeof usePublicDirectory !== 'boolean') {
|
|
1660
|
+
throw new Error('usePublicDirectory must be a boolean');
|
|
1661
|
+
}
|
|
1662
|
+
if (type === 'parked' && (directory !== null || usePublicDirectory !== null)) {
|
|
1663
|
+
throw new Error('directory and usePublicDirectory apply only to type subdomain');
|
|
1664
|
+
}
|
|
1665
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1666
|
+
throw new Error('domain is required and must be a string');
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
let prefix = '';
|
|
1670
|
+
if (type === 'subdomain') {
|
|
1671
|
+
const suffix = '.' + requestedDomain.toLowerCase();
|
|
1672
|
+
prefix = name.toLowerCase().endsWith(suffix) ? name.slice(0, -suffix.length) : name;
|
|
1673
|
+
if (prefix === '' || (prefix === name && name.includes('.'))) {
|
|
1674
|
+
throw new Error('name must be a subdomain prefix (blog) or a name under ' + requestedDomain + ' (blog.' + requestedDomain + ')');
|
|
1675
|
+
}
|
|
1676
|
+
if (prefix.toLowerCase() === 'www') {
|
|
1677
|
+
throw new Error('www cannot be added as a subdomain: it already belongs to ' + requestedDomain);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1682
|
+
|
|
1683
|
+
if (type === 'parked') {
|
|
1684
|
+
const response = await this.callOperation('hosting_domains_create-website-parked', { username, domain, parked_domain: name });
|
|
1685
|
+
return { domain, type, name, response };
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const response = await this.callOperation('hosting_domains_create-website-subdomain', {
|
|
1689
|
+
username,
|
|
1690
|
+
domain,
|
|
1691
|
+
subdomain: prefix,
|
|
1692
|
+
...(directory !== null ? { directory } : {}),
|
|
1693
|
+
...(usePublicDirectory !== null ? { is_using_public_directory: usePublicDirectory } : {}),
|
|
1694
|
+
});
|
|
1695
|
+
return { domain, type, name: prefix + '.' + domain, response };
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
private async handleHostingWebsitesRemoveDomain(params: Record<string, any>): Promise<any> {
|
|
1699
|
+
const { domain: requestedDomain, name } = params;
|
|
1700
|
+
|
|
1701
|
+
if (!name || typeof name !== 'string') {
|
|
1702
|
+
throw new Error('name is required and must be a string');
|
|
1703
|
+
}
|
|
1704
|
+
if (!requestedDomain || typeof requestedDomain !== 'string') {
|
|
1705
|
+
throw new Error('domain is required and must be a string');
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
const { username, domain } = await this.findWebsite(requestedDomain);
|
|
1709
|
+
const target = { username, domain };
|
|
1710
|
+
|
|
1711
|
+
const [parkedResponse, subdomainsResponse] = await Promise.all([
|
|
1712
|
+
this.callOperation('hosting_domains_list-website-parked', target),
|
|
1713
|
+
this.callOperation('hosting_domains_list-website-subdomains', target),
|
|
1714
|
+
]);
|
|
1715
|
+
const parkedDomains = this.domainItems(parkedResponse);
|
|
1716
|
+
const subdomains = this.domainItems(subdomainsResponse);
|
|
1717
|
+
const wanted = name.toLowerCase();
|
|
1718
|
+
|
|
1719
|
+
const parked = parkedDomains.find((item: any) => typeof item.domain === 'string' && item.domain.toLowerCase() === wanted);
|
|
1720
|
+
if (parked) {
|
|
1721
|
+
const response = await this.callOperation('hosting_domains_delete-website-parked', { ...target, parkedDomain: parked.domain });
|
|
1722
|
+
return { domain, type: 'parked', name: parked.domain, response };
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
const subdomain = subdomains.find((item: any) => (typeof item.domain === 'string' && item.domain.toLowerCase() === wanted)
|
|
1726
|
+
|| (typeof item.subdomain === 'string' && item.subdomain.toLowerCase() === wanted));
|
|
1727
|
+
if (subdomain) {
|
|
1728
|
+
const response = await this.callOperation('hosting_domains_delete-website-subdomain', { ...target, subdomain: subdomain.subdomain });
|
|
1729
|
+
return { domain, type: 'subdomain', name: subdomain.domain, response };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
const listed = (items: any[]): string => items.map((item: any) => item.domain).filter(Boolean).join(', ') || 'none';
|
|
1733
|
+
throw new Error(name + ' is not a parked domain or subdomain of ' + domain + '. Parked domains: ' + listed(parkedDomains) + '. Subdomains: ' + listed(subdomains) + '.');
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
private async handleHostingWebsitesStartSetup(params: Record<string, any>): Promise<any> {
|
|
1737
|
+
// Models send null for fields they leave unset; the setup endpoint rejects some of them.
|
|
1738
|
+
const forwarded = Object.fromEntries(Object.entries(params).filter(([, value]) => value !== null && value !== undefined));
|
|
1739
|
+
return this.callOperation('hosting_websites_create', forwarded);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1588
1742
|
private hosting_importWordpressWebsite_validateArchiveFormat(filePath: string): boolean {
|
|
1589
1743
|
const validExtensions = ['zip', 'tar', 'tar.gz', 'tgz', '7z', 'gz', 'gzip'];
|
|
1590
1744
|
const fileName = path.basename(filePath).toLowerCase();
|