hosting_deployWordpressTheme
Upload a WordPress theme from a local directory to your hosting server and optionally activate it after deployment.
Instructions
Deploy a WordPress theme from a directory to a hosting server. This tool uploads all theme files and triggers theme deployment. The uploaded theme can optionally be activated after deployment.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes | Domain name associated with the hosting account (e.g., example.com) | |
| slug | Yes | WordPress theme slug (e.g., twentytwentyfive) | |
| themePath | Yes | Absolute or relative path to the theme directory containing all theme files | |
| activate | No | Whether to activate the theme after deployment (default: false) |
Implementation Reference
- src/core/tools/hosting.ts:83-121 (schema)Schema/definition for the hosting_deployWordpressTheme tool. Defines the tool with name, description, inputSchema (domain, slug, themePath, activate), and references handlerMethod 'handleWordpressThemeDeploy'.
{ "name": "hosting_deployWordpressTheme", "topic": "hosting", "description": "Deploy a WordPress theme from a directory to a hosting server. This tool uploads all theme files and triggers theme deployment. The uploaded theme can optionally be activated after deployment.", "method": "", "path": "", "inputSchema": { "type": "object", "properties": { "domain": { "type": "string", "description": "Domain name associated with the hosting account (e.g., example.com)" }, "slug": { "type": "string", "description": "WordPress theme slug (e.g., twentytwentyfive)" }, "themePath": { "type": "string", "description": "Absolute or relative path to the theme directory containing all theme files" }, "activate": { "type": "boolean", "description": "Whether to activate the theme after deployment (default: false)" } }, "required": [ "domain", "slug", "themePath" ] }, "security": [], "custom": true, "templateFile": "deploy-wordpress-theme.template.js", "templateFileTS": "deploy-wordpress-theme.template.ts", "handlerMethod": "handleWordpressThemeDeploy", "group": "hosting" }, - src/core/runtime.ts:1060-1177 (handler)Main handler function for hosting_deployWordpressTheme. Validates params, resolves username from domain, generates a unique upload dir name, scans the theme directory, uploads all files via TUS to wp-content/themes/, and finally triggers theme deployment via the Hostinger API with optional activation.
private async handleWordpressThemeDeploy(params: Record<string, any>): Promise<any> { const { domain, slug, themePath, activate = false } = params; this.hosting_deployWordpressTheme_validateRequiredParams(params); this.hosting_deployWordpressTheme_validateThemeDirectory(themePath); this.log('info', `Resolving username from domain: ${domain}`); const username = await this.resolveUsername(domain); const randomSuffix = this.hosting_deployWordpressTheme_generateRandomString(8); const uploadDirName = `${slug}-${randomSuffix}`; this.log('info', `Scanning theme directory: ${themePath}`); const themeFiles = this.hosting_deployWordpressTheme_scanDirectory(themePath); if (themeFiles.length === 0) { throw new Error(`No files found in theme directory: ${themePath}`); } this.log('info', `Found ${themeFiles.length} files to upload`); let uploadCredentials: any; try { uploadCredentials = await this.fetchUploadCredentials(username, domain); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to fetch upload credentials: ${errorMessage}`); } const { url: uploadUrl, auth_key: authToken, rest_auth_key: authRestToken } = uploadCredentials; if (!uploadUrl || !authToken || !authRestToken) { throw new Error('Invalid upload credentials received from API'); } this.log('info', `Starting theme file upload to ${uploadUrl}`); const results: any[] = []; let successCount = 0; let failureCount = 0; for (const fileInfo of themeFiles) { try { const normalizedRelativePath = this.normalizePath(fileInfo.relativePath); const uploadPath = `wp-content/themes/${uploadDirName}/${normalizedRelativePath}`; this.log('info', `Uploading: ${fileInfo.absolutePath} -> ${uploadPath}`); const stats = fs.statSync(fileInfo.absolutePath); const uploadResult = await this.uploadFile( fileInfo.absolutePath, uploadPath, uploadUrl, authRestToken, authToken ); results.push({ file: fileInfo.absolutePath, remotePath: uploadPath, status: 'success', uploadUrl: uploadResult.url, size: stats.size }); successCount++; this.log('info', `Successfully uploaded: ${uploadPath}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const normalizedRelativePath = this.normalizePath(fileInfo.relativePath); const uploadPath = `wp-content/themes/${uploadDirName}/${normalizedRelativePath}`; results.push({ file: fileInfo.absolutePath, remotePath: uploadPath, status: 'error', error: errorMessage }); failureCount++; this.log('error', `Failed to upload ${fileInfo.absolutePath}: ${errorMessage}`); } } const overallStatus = failureCount === 0 ? 'success' : (successCount === 0 ? 'failure' : 'partial'); if (failureCount === 0) { try { this.log('info', 'All files uploaded successfully, triggering theme deployment...'); await this.hosting_deployWordpressTheme_deployTheme(username, domain, slug, uploadDirName, activate); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.log('error', `Theme deployment failed: ${errorMessage}`); return { status: 'partial', summary: { total: themeFiles.length, successful: successCount, failed: failureCount }, results, deploymentError: errorMessage }; } } return { status: overallStatus, summary: { total: themeFiles.length, successful: successCount, failed: failureCount }, results, uploadDirName, activated: activate }; } - src/core/runtime.ts:201-220 (registration)Registration of hosting_deployWordpressTheme in the executeCustomTool switch statement, routing tool name to the handleWordpressThemeDeploy handler.
private async executeCustomTool(tool: OpenApiTool, params: Record<string, any>): Promise<any> { switch (tool.name) { case 'hosting_importWordpressWebsite': return await this.handleWordpressWebsiteImport(params); case 'hosting_deployWordpressPlugin': return await this.handleWordpressPluginDeploy(params); case 'hosting_deployWordpressTheme': return await this.handleWordpressThemeDeploy(params); case 'hosting_deployJsApplication': return await this.handleJavascriptApplicationDeploy(params); case 'hosting_deployStaticWebsite': return await this.handleStaticWebsiteDeploy(params); case 'hosting_listJsDeployments': return await this.handleListJavascriptDeployments(params); case 'hosting_showJsDeploymentLogs': return await this.handleShowJsDeploymentLogs(params); default: throw new Error(`Unknown custom tool: ${tool.name}`); } } - src/core/runtime.ts:979-1003 (helper)Helper that recursively scans a theme directory, returning all files with their absolute and relative paths.
private hosting_deployWordpressTheme_scanDirectory(dirPath: string, basePath: string = dirPath): Array<{absolutePath: string, relativePath: string}> { const files: Array<{absolutePath: string, relativePath: string}> = []; const scanDir = (currentPath: string) => { const items = fs.readdirSync(currentPath); for (const item of items) { const itemPath = path.join(currentPath, item); const stats = fs.statSync(itemPath); if (stats.isDirectory()) { scanDir(itemPath); } else if (stats.isFile()) { const relativePath = path.relative(basePath, itemPath); files.push({ absolutePath: itemPath, relativePath: relativePath }); } } }; scanDir(dirPath); return files; } - src/core/runtime.ts:1005-1058 (helper)Helper that calls the Hostinger API endpoint POST /api/hosting/v1/accounts/{username}/websites/{domain}/wordpress/themes/deploy to trigger the theme deployment with optional activation (is_activated).
private async hosting_deployWordpressTheme_deployTheme(username: string, domain: string, slug: string, themePath: string, activate: boolean = false): Promise<boolean> { const baseUrl = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`; const url = new URL(`api/hosting/v1/accounts/${username}/websites/${domain}/wordpress/themes/deploy`, baseUrl).toString(); try { const bearerToken = process.env['API_TOKEN'] || process.env['APITOKEN']; if (!bearerToken) { throw new Error('API_TOKEN environment variable not found'); } const config: AxiosRequestConfig = { method: 'post', url, headers: { ...this.headers, 'Authorization': `Bearer ${bearerToken}`, 'Content-Type': 'application/json' }, data: { slug, theme_path: themePath, is_activated: activate }, timeout: 60000, // 60s validateStatus: function (status: number): boolean { return status < 500; } }; const response = await axios(config); if (response.status !== 200) { throw new Error(`API returned status ${response.status}: ${JSON.stringify(response.data)}`); } this.log('info', `Successfully triggered theme deployment for ${domain}`); return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.log('error', `Failed to trigger theme deployment: ${errorMessage}`); if (axios.isAxiosError(error)) { const responseData = error.response?.data; const responseStatus = error.response?.status; this.log('error', 'API Error Details:', { status: responseStatus, data: typeof responseData === 'object' ? JSON.stringify(responseData) : responseData }); } throw error; } }