From 576ca9e18349db40da70802eaa693f043e5ee22e Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Fri, 14 Jun 2024 17:50:41 +0300 Subject: [PATCH 1/7] Studio: Add chat context --- src/components/content-tab-assistant.tsx | 5 + src/hooks/use-chat-context.ts | 125 +++++++++++++++++++++++ src/ipc-handlers.ts | 32 +++++- src/lib/php-get.ts | 75 ++++++++++++++ src/preload.ts | 2 + src/site-server.ts | 2 +- 6 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/hooks/use-chat-context.ts create mode 100644 src/lib/php-get.ts diff --git a/src/components/content-tab-assistant.tsx b/src/components/content-tab-assistant.tsx index 0138d3fc0..1fb2800ba 100644 --- a/src/components/content-tab-assistant.tsx +++ b/src/components/content-tab-assistant.tsx @@ -8,6 +8,7 @@ import Markdown, { ExtraProps } from 'react-markdown'; import { useAssistant, Message as MessageType } from '../hooks/use-assistant'; import { useAssistantApi } from '../hooks/use-assistant-api'; import { useAuth } from '../hooks/use-auth'; +import { useChatContext } from '../hooks/use-chat-context'; import { useFetchWelcomeMessages } from '../hooks/use-fetch-welcome-messages'; import { useOffline } from '../hooks/use-offline'; import { usePromptUsage } from '../hooks/use-prompt-usage'; @@ -234,6 +235,7 @@ const UnauthenticatedView = ( { onAuthenticate }: { onAuthenticate: () => void } ); export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps ) { + const siteContext = useChatContext( selectedSite ); const { messages, addMessage, chatId, clearMessages } = useAssistant( selectedSite.name ); const { userCanSendMessage } = usePromptUsage(); const { fetchAssistant, isLoading: isAssistantThinking } = useAssistantApi(); @@ -247,6 +249,9 @@ export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps const { isAuthenticated, authenticate } = useAuth(); const isOffline = useOffline(); const { __ } = useI18n(); + useEffect( () => { + console.log( 'context', siteContext ); + }, [ siteContext ] ); useEffect( () => { fetchWelcomeMessages(); diff --git a/src/hooks/use-chat-context.ts b/src/hooks/use-chat-context.ts new file mode 100644 index 000000000..a38e39e3e --- /dev/null +++ b/src/hooks/use-chat-context.ts @@ -0,0 +1,125 @@ +import { useState, useEffect, useMemo, useCallback } from 'react'; +import { getIpcApi } from '../lib/get-ipc-api'; +import { useGetWpVersion } from './use-get-wp-version'; +import { useSiteDetails } from './use-site-details'; +import { useWindowListener } from './use-window-listener'; + +export interface SiteContext { + currentURL: string; + pluginList: string[]; + themeList: string[]; + numberOfSites: number; + themeName?: string; + wpVersion: string; + phpVersion: string; + isBlockTheme?: boolean; +} + +function parseWpCliOutput( stdout: string, defaultValue: string[] ): string[] { + try { + const data = JSON.parse( stdout ); + return data?.map( ( item: { name: string } ) => item.name ) || []; + } catch ( error ) { + console.error( error ); + } + return defaultValue; +} + +export const useChatContext = ( selectedSite: SiteDetails ) => { + const [ initialLoad, setInitialLoad ] = useState< Record< string, boolean > >( {} ); + const { data: sites, loadingSites } = useSiteDetails(); + const wpVersion = useGetWpVersion( selectedSite || ( {} as SiteDetails ) ); + const [ pluginsList, setPluginsList ] = useState< Record< string, string[] > >( {} ); + const [ themesList, setThemesList ] = useState< Record< string, string[] > >( {} ); + const numberOfSites = sites?.length || 0; + const { path, port, themeDetails } = selectedSite; + + const fetchPluginList = useCallback( async ( path: string ) => { + const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { + projectPath: path, + args: [ 'plugin', 'list', '--format=json', '--status=active' ], + } ); + if ( stderr ) { + return []; + } + return parseWpCliOutput( stdout, [] ); + }, [] ); + + const fetchThemeList = useCallback( async ( path: string ) => { + const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { + projectPath: path, + args: [ 'theme', 'list', '--format=json' ], + } ); + if ( stderr ) { + return []; + } + return parseWpCliOutput( stdout, [] ); + }, [] ); + + useEffect( () => { + let isCurrent = true; + // Initial load. Prefetch all the plugins and themes for the sites. + const run = async () => { + const isCurrent = true; + const result = await Promise.all( [ + fetchPluginList( selectedSite.path ), + fetchThemeList( selectedSite.path ), + ] ); + if ( isCurrent ) { + setInitialLoad( ( prev ) => ( { ...prev, [ selectedSite.id ]: true } ) ); + setPluginsList( ( prev ) => ( { ...prev, [ selectedSite.id ]: result[ 0 ] } ) ); + setThemesList( ( prev ) => ( { ...prev, [ selectedSite.id ]: result[ 1 ] } ) ); + } + }; + if ( selectedSite && ! loadingSites && ! initialLoad[ selectedSite.id ] && isCurrent ) { + run(); + } + return () => { + isCurrent = false; + }; + }, [ + fetchPluginList, + fetchThemeList, + initialLoad, + loadingSites, + pluginsList, + selectedSite, + sites, + themesList, + ] ); + + useWindowListener( 'focus', async () => { + // When the window is focused, we need to kick off a request to refetch the theme details, if server is running. + if ( ! selectedSite?.id || selectedSite.running === false ) { + return; + } + const plugins = await fetchPluginList( path ); + const themes = await fetchThemeList( path ); + pluginsList[ selectedSite.id ] = plugins; + themesList[ selectedSite.id ] = themes; + } ); + const siteContext: SiteContext = useMemo( () => { + return { + numberOfSites, + themeList: themesList[ selectedSite.id ] || [], + pluginList: pluginsList[ selectedSite.id ] || [], + wpVersion, + // This will be fetched by a hook when php selection is merged + phpVersion: '8.0', + currentURL: `http://localhost:${ port }`, + themeName: themeDetails?.name, + isBlockTheme: themeDetails?.isBlockTheme, + }; + }, [ + numberOfSites, + themesList, + selectedSite.id, + pluginsList, + wpVersion, + port, + themeDetails?.name, + themeDetails?.isBlockTheme, + ] ); + + return siteContext; +}; diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index 81a2199d8..7a9cf8335 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -25,7 +25,8 @@ import { isInstalled } from './lib/is-installed'; import { getLocaleData, getSupportedLocale } from './lib/locale'; import * as oauthClient from './lib/oauth'; import { createPassword } from './lib/passwords'; -import { phpGetThemeDetails } from './lib/php-get-theme-details'; +import { phpGetAllPlugins, phpGetAllThemes } from './lib/php-get'; +import { phpGetThemeDetails } from './lib/php-get-theme-details' import { sanitizeForLogging } from './lib/sanitize-for-logging'; import { sortSites } from './lib/sort-sites'; import { @@ -545,6 +546,35 @@ export async function getThemeDetails( event: IpcMainInvokeEvent, id: string ) { return themeDetails; } +export async function getAllPlugins( event: IpcMainInvokeEvent, id: string ) { + const server = SiteServer.get( id ); + if ( ! server ) { + throw new Error( 'Site not found.' ); + } + + if ( ! server.details.running || ! server.server ) { + return null; + } + const allPlugins = await phpGetAllPlugins( server.server ); + console.log( 'All plugins', allPlugins ); + return allPlugins; +} + +export async function getAllThemes( event: IpcMainInvokeEvent, id: string ) { + const server = SiteServer.get( id ); + if ( ! server ) { + throw new Error( 'Site not found.' ); + } + + if ( ! server.details.running || ! server.server ) { + return null; + } + console.log( 'Getting all themes' ); + const allThemes = await phpGetAllThemes( server.server ); + console.log( 'All themes', allThemes ); + return allThemes; +} + export async function getOnboardingData( _event: IpcMainInvokeEvent ): Promise< boolean > { const userData = await loadUserData(); const { onboardingCompleted = false } = userData; diff --git a/src/lib/php-get.ts b/src/lib/php-get.ts new file mode 100644 index 000000000..38237cf5e --- /dev/null +++ b/src/lib/php-get.ts @@ -0,0 +1,75 @@ +import * as Sentry from '@sentry/electron/main'; +import SiteServerProcess from './site-server-process'; + +export async function runArbitraryPhpCode( + server: SiteServerProcess, + phpCode: string +): Promise< unknown > { + if ( ! server.php ) { + throw Error( 'PHP is not instantiated' ); + } + + let result = null; + try { + result = await server.runPhp( { + code: phpCode, + } ); + result = JSON.parse( result ); + } catch ( error ) { + Sentry.captureException( error, { + extra: { result }, + } ); + result = null; + } + return result; +} + +export async function phpGetThemeDetails( + server: SiteServerProcess +): Promise< StartedSiteDetails[ 'themeDetails' ] > { + if ( ! server.php ) { + throw Error( 'PHP is not instantiated' ); + } + + const phpCode = ` + require_once('${ server.php.documentRoot }/wp-load.php'); + $theme = wp_get_theme(); + echo json_encode([ + 'name' => $theme->get('Name'), + 'uri' => $theme->get('ThemeURI'), + 'path' => $theme->get_stylesheet_directory(), + 'slug' => $theme->get_stylesheet(), + 'isBlockTheme' => $theme->is_block_theme(), + 'supportsWidgets' => current_theme_supports('widgets'), + 'supportsMenus' => get_registered_nav_menus() || current_theme_supports('menus'), + ]); + `; + + return runArbitraryPhpCode( server, phpCode ) as Promise< StartedSiteDetails[ 'themeDetails' ] >; +} + +export async function phpGetAllPlugins( server: SiteServerProcess ): Promise< unknown > { + if ( ! server.php ) { + throw Error( 'PHP is not instantiated' ); + } + const phpCode = ` + require_once('${ server.php.documentRoot }/wp-load.php'); + $plugins = get_plugins(); + echo json_encode($plugins); + `; + + return runArbitraryPhpCode( server, phpCode ); +} + +export async function phpGetAllThemes( server: SiteServerProcess ): Promise< unknown > { + if ( ! server.php ) { + throw Error( 'PHP is not instantiated' ); + } + const phpCode = ` + require_once('${ server.php.documentRoot }/wp-load.php'); + $themes = wp_get_themes(); + echo json_encode($themes); + `; + + return runArbitraryPhpCode( server, phpCode ); +} diff --git a/src/preload.ts b/src/preload.ts index 65684e8fe..e79b7263d 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -35,6 +35,8 @@ const api: IpcApi = { ipcRenderer.invoke( 'generateProposedSitePath', siteName ), openLocalPath: ( path: string ) => ipcRenderer.invoke( 'openLocalPath', path ), getThemeDetails: ( id: string ) => ipcRenderer.invoke( 'getThemeDetails', id ), + getAllPlugins: ( id: string ) => ipcRenderer.invoke( 'getAllPlugins', id ), + getAllThemes: ( id: string ) => ipcRenderer.invoke( 'getAllThemes', id ), getThumbnailData: ( id: string ) => ipcRenderer.invoke( 'getThumbnailData', id ), getInstalledApps: () => ipcRenderer.invoke( 'getInstalledApps' ), executeWPCLiInline: ( options: { projectPath: string; args: string[] } ) => diff --git a/src/site-server.ts b/src/site-server.ts index 05f8e585a..b5e290a70 100644 --- a/src/site-server.ts +++ b/src/site-server.ts @@ -6,7 +6,7 @@ import { WPNowMode } from '../vendor/wp-now/src/config'; import { getWordPressVersionPath } from '../vendor/wp-now/src/download'; import { pathExists, recursiveCopyDirectory, isEmptyDir } from './lib/fs-utils'; import { decodePassword } from './lib/passwords'; -import { phpGetThemeDetails } from './lib/php-get-theme-details'; +import { phpGetThemeDetails } from './lib/php-get'; import { portFinder } from './lib/port-finder'; import { sanitizeForLogging } from './lib/sanitize-for-logging'; import { getPreferredSiteLanguage } from './lib/site-language'; From 98986614f4677fbc7dffa217a624937875fee747 Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Mon, 17 Jun 2024 12:13:02 +0300 Subject: [PATCH 2/7] Update: Fix Wp execute error and clean up code --- src/components/content-tab-assistant.tsx | 14 ++- src/components/root.tsx | 5 +- src/hooks/use-assistant-api.ts | 21 +++- ...e-chat-context.ts => use-chat-context.tsx} | 98 ++++++++++++++----- src/ipc-handlers.ts | 41 ++------ src/lib/php-get.ts | 75 -------------- src/preload.ts | 10 +- src/site-server.ts | 2 +- vendor/wp-now/src/execute-wp-cli.ts | 22 ++--- 9 files changed, 125 insertions(+), 163 deletions(-) rename src/hooks/{use-chat-context.ts => use-chat-context.tsx} (53%) delete mode 100644 src/lib/php-get.ts diff --git a/src/components/content-tab-assistant.tsx b/src/components/content-tab-assistant.tsx index 1fb2800ba..8df03e2e7 100644 --- a/src/components/content-tab-assistant.tsx +++ b/src/components/content-tab-assistant.tsx @@ -235,7 +235,7 @@ const UnauthenticatedView = ( { onAuthenticate }: { onAuthenticate: () => void } ); export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps ) { - const siteContext = useChatContext( selectedSite ); + const currentSiteChatContext = useChatContext(); const { messages, addMessage, chatId, clearMessages } = useAssistant( selectedSite.name ); const { userCanSendMessage } = usePromptUsage(); const { fetchAssistant, isLoading: isAssistantThinking } = useAssistantApi(); @@ -249,9 +249,6 @@ export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps const { isAuthenticated, authenticate } = useAuth(); const isOffline = useOffline(); const { __ } = useI18n(); - useEffect( () => { - console.log( 'context', siteContext ); - }, [ siteContext ] ); useEffect( () => { fetchWelcomeMessages(); @@ -263,10 +260,11 @@ export function ContentTabAssistant( { selectedSite }: ContentTabAssistantProps addMessage( chatMessage, 'user', chatId ); setInput( '' ); try { - const { message, chatId: fetchedChatId } = await fetchAssistant( chatId, [ - ...messages, - { content: chatMessage, role: 'user' }, - ] ); + const { message, chatId: fetchedChatId } = await fetchAssistant( + chatId, + [ ...messages, { content: chatMessage, role: 'user' } ], + currentSiteChatContext + ); if ( message ) { addMessage( message, 'assistant', chatId ?? fetchedChatId ); } diff --git a/src/components/root.tsx b/src/components/root.tsx index 2c36846be..8231bcef0 100644 --- a/src/components/root.tsx +++ b/src/components/root.tsx @@ -1,3 +1,4 @@ +import { ChatProvider } from '../hooks/use-chat-context'; import { InstalledAppsProvider } from '../hooks/use-check-installed-apps'; import { OnboardingProvider } from '../hooks/use-onboarding'; import { PromptUsageProvider } from '../hooks/use-prompt-usage'; @@ -20,7 +21,9 @@ const Root = () => { - + + + diff --git a/src/hooks/use-assistant-api.ts b/src/hooks/use-assistant-api.ts index e50271c8b..3d2ad637c 100644 --- a/src/hooks/use-assistant-api.ts +++ b/src/hooks/use-assistant-api.ts @@ -1,15 +1,32 @@ import { useCallback, useState } from 'react'; import { Message } from './use-assistant'; import { useAuth } from './use-auth'; +import { ChatContextType } from './use-chat-context'; import { usePromptUsage } from './use-prompt-usage'; +const contextMapper = ( context?: ChatContextType ) => { + if ( ! context ) { + return {}; + } + return { + current_url: context.currentURL, + number_of_sites: context.numberOfSites, + wp_version: context.wpVersion, + php_version: context.phpVersion, + plugins: context.pluginList, + themes: context.themeList, + current_theme: context.themeName, + is_block_theme: context.isBlockTheme, + }; +}; + export function useAssistantApi() { const { client } = useAuth(); const [ isLoading, setIsLoading ] = useState( false ); const { updatePromptUsage } = usePromptUsage(); const fetchAssistant = useCallback( - async ( chatId: string | undefined, messages: Message[] ) => { + async ( chatId: string | undefined, messages: Message[], context?: ChatContextType ) => { if ( ! client ) { throw new Error( 'WPcom client not initialized' ); } @@ -17,7 +34,7 @@ export function useAssistantApi() { const body = { messages, chat_id: chatId, - context: [], + context: contextMapper( context ), }; let response; let headers; diff --git a/src/hooks/use-chat-context.ts b/src/hooks/use-chat-context.tsx similarity index 53% rename from src/hooks/use-chat-context.ts rename to src/hooks/use-chat-context.tsx index a38e39e3e..51e953088 100644 --- a/src/hooks/use-chat-context.ts +++ b/src/hooks/use-chat-context.tsx @@ -1,10 +1,18 @@ -import { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { + createContext, + useContext, + useMemo, + useState, + useEffect, + useCallback, + ReactNode, +} from 'react'; import { getIpcApi } from '../lib/get-ipc-api'; import { useGetWpVersion } from './use-get-wp-version'; import { useSiteDetails } from './use-site-details'; import { useWindowListener } from './use-window-listener'; -export interface SiteContext { +export interface ChatContextType { currentURL: string; pluginList: string[]; themeList: string[]; @@ -14,8 +22,22 @@ export interface SiteContext { phpVersion: string; isBlockTheme?: boolean; } +const ChatContext = createContext< ChatContextType >( { + currentURL: '', + pluginList: [] as string[], + themeList: [] as string[], + numberOfSites: 0, + themeName: '', + phpVersion: '', + isBlockTheme: false, + wpVersion: '', +} ); -function parseWpCliOutput( stdout: string, defaultValue: string[] ): string[] { +interface ChatProviderProps { + children: ReactNode; +} + +const parseWpCliOutput = ( stdout: string, defaultValue: string[] ): string[] => { try { const data = JSON.parse( stdout ); return data?.map( ( item: { name: string } ) => item.name ) || []; @@ -23,21 +45,25 @@ function parseWpCliOutput( stdout: string, defaultValue: string[] ): string[] { console.error( error ); } return defaultValue; -} +}; -export const useChatContext = ( selectedSite: SiteDetails ) => { +export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const [ initialLoad, setInitialLoad ] = useState< Record< string, boolean > >( {} ); - const { data: sites, loadingSites } = useSiteDetails(); + const { data: sites, loadingSites, selectedSite } = useSiteDetails(); const wpVersion = useGetWpVersion( selectedSite || ( {} as SiteDetails ) ); const [ pluginsList, setPluginsList ] = useState< Record< string, string[] > >( {} ); const [ themesList, setThemesList ] = useState< Record< string, string[] > >( {} ); const numberOfSites = sites?.length || 0; - const { path, port, themeDetails } = selectedSite; + const sitePath = selectedSite?.path || ''; + const sitePort = selectedSite?.port || ''; + const siteThemeDetails = selectedSite?.themeDetails; const fetchPluginList = useCallback( async ( path: string ) => { const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { projectPath: path, args: [ 'plugin', 'list', '--format=json', '--status=active' ], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + forcedWPNowOptions: { mode: 'index' as any }, } ); if ( stderr ) { return []; @@ -49,6 +75,8 @@ export const useChatContext = ( selectedSite: SiteDetails ) => { const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { projectPath: path, args: [ 'theme', 'list', '--format=json' ], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + forcedWPNowOptions: { mode: 'index' as any }, } ); if ( stderr ) { return []; @@ -60,18 +88,24 @@ export const useChatContext = ( selectedSite: SiteDetails ) => { let isCurrent = true; // Initial load. Prefetch all the plugins and themes for the sites. const run = async () => { - const isCurrent = true; const result = await Promise.all( [ - fetchPluginList( selectedSite.path ), - fetchThemeList( selectedSite.path ), + fetchPluginList( sitePath ), + fetchThemeList( sitePath ), ] ); - if ( isCurrent ) { + if ( isCurrent && selectedSite?.id ) { setInitialLoad( ( prev ) => ( { ...prev, [ selectedSite.id ]: true } ) ); setPluginsList( ( prev ) => ( { ...prev, [ selectedSite.id ]: result[ 0 ] } ) ); setThemesList( ( prev ) => ( { ...prev, [ selectedSite.id ]: result[ 1 ] } ) ); } }; - if ( selectedSite && ! loadingSites && ! initialLoad[ selectedSite.id ] && isCurrent ) { + if ( + selectedSite && + ! loadingSites && + ! initialLoad[ selectedSite.id ] && + isCurrent && + ! pluginsList[ selectedSite.id ] && + ! themesList[ selectedSite.id ] + ) { run(); } return () => { @@ -86,6 +120,7 @@ export const useChatContext = ( selectedSite: SiteDetails ) => { selectedSite, sites, themesList, + sitePath, ] ); useWindowListener( 'focus', async () => { @@ -93,33 +128,42 @@ export const useChatContext = ( selectedSite: SiteDetails ) => { if ( ! selectedSite?.id || selectedSite.running === false ) { return; } - const plugins = await fetchPluginList( path ); - const themes = await fetchThemeList( path ); - pluginsList[ selectedSite.id ] = plugins; - themesList[ selectedSite.id ] = themes; + const plugins = await fetchPluginList( sitePath ); + const themes = await fetchThemeList( sitePath ); + setPluginsList( ( prev ) => ( { ...prev, [ selectedSite.id ]: plugins } ) ); + setThemesList( ( prev ) => ( { ...prev, [ selectedSite.id ]: themes } ) ); } ); - const siteContext: SiteContext = useMemo( () => { + + const contextValue = useMemo( () => { return { numberOfSites, - themeList: themesList[ selectedSite.id ] || [], - pluginList: pluginsList[ selectedSite.id ] || [], + themeList: selectedSite?.id ? themesList[ selectedSite.id ] || [] : [], + pluginList: selectedSite?.id ? pluginsList[ selectedSite.id ] || [] : [], wpVersion, // This will be fetched by a hook when php selection is merged phpVersion: '8.0', - currentURL: `http://localhost:${ port }`, - themeName: themeDetails?.name, - isBlockTheme: themeDetails?.isBlockTheme, + currentURL: `http://localhost:${ sitePort }`, + themeName: siteThemeDetails?.name, + isBlockTheme: siteThemeDetails?.isBlockTheme, }; }, [ numberOfSites, themesList, - selectedSite.id, + selectedSite?.id, pluginsList, wpVersion, - port, - themeDetails?.name, - themeDetails?.isBlockTheme, + sitePort, + siteThemeDetails?.name, + siteThemeDetails?.isBlockTheme, ] ); - return siteContext; + return { children }; +}; + +export const useChatContext = (): ChatContextType => { + const context = useContext( ChatContext ); + if ( ! context ) { + throw new Error( 'useChatContext must be used within a ChatProvider' ); + } + return context; }; diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index 7a9cf8335..2c27c01f3 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -14,6 +14,7 @@ import nodePath from 'path'; import * as Sentry from '@sentry/electron/main'; import archiver from 'archiver'; import { copySync } from 'fs-extra'; +import { WPNowOptions } from '../vendor/wp-now/src/config'; import { SQLITE_FILENAME } from '../vendor/wp-now/src/constants'; import { downloadSqliteIntegrationPlugin } from '../vendor/wp-now/src/download'; import { executeWPCli } from '../vendor/wp-now/src/execute-wp-cli'; @@ -25,8 +26,7 @@ import { isInstalled } from './lib/is-installed'; import { getLocaleData, getSupportedLocale } from './lib/locale'; import * as oauthClient from './lib/oauth'; import { createPassword } from './lib/passwords'; -import { phpGetAllPlugins, phpGetAllThemes } from './lib/php-get'; -import { phpGetThemeDetails } from './lib/php-get-theme-details' +import { phpGetThemeDetails } from './lib/php-get-theme-details'; import { sanitizeForLogging } from './lib/sanitize-for-logging'; import { sortSites } from './lib/sort-sites'; import { @@ -546,35 +546,6 @@ export async function getThemeDetails( event: IpcMainInvokeEvent, id: string ) { return themeDetails; } -export async function getAllPlugins( event: IpcMainInvokeEvent, id: string ) { - const server = SiteServer.get( id ); - if ( ! server ) { - throw new Error( 'Site not found.' ); - } - - if ( ! server.details.running || ! server.server ) { - return null; - } - const allPlugins = await phpGetAllPlugins( server.server ); - console.log( 'All plugins', allPlugins ); - return allPlugins; -} - -export async function getAllThemes( event: IpcMainInvokeEvent, id: string ) { - const server = SiteServer.get( id ); - if ( ! server ) { - throw new Error( 'Site not found.' ); - } - - if ( ! server.details.running || ! server.server ) { - return null; - } - console.log( 'Getting all themes' ); - const allThemes = await phpGetAllThemes( server.server ); - console.log( 'All themes', allThemes ); - return allThemes; -} - export async function getOnboardingData( _event: IpcMainInvokeEvent ): Promise< boolean > { const userData = await loadUserData(); const { onboardingCompleted = false } = userData; @@ -594,9 +565,13 @@ export async function saveOnboarding( export async function executeWPCLiInline( _event: IpcMainInvokeEvent, - { projectPath, args }: { projectPath: string; args: string[] } + { + projectPath, + args, + forcedWPNowOptions, + }: { projectPath: string; args: string[]; forcedWPNowOptions?: WPNowOptions } ) { - const { stdout, stderr } = await executeWPCli( projectPath, args ); + const { stdout, stderr } = await executeWPCli( projectPath, args, forcedWPNowOptions ); return { stdout, stderr }; } diff --git a/src/lib/php-get.ts b/src/lib/php-get.ts deleted file mode 100644 index 38237cf5e..000000000 --- a/src/lib/php-get.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as Sentry from '@sentry/electron/main'; -import SiteServerProcess from './site-server-process'; - -export async function runArbitraryPhpCode( - server: SiteServerProcess, - phpCode: string -): Promise< unknown > { - if ( ! server.php ) { - throw Error( 'PHP is not instantiated' ); - } - - let result = null; - try { - result = await server.runPhp( { - code: phpCode, - } ); - result = JSON.parse( result ); - } catch ( error ) { - Sentry.captureException( error, { - extra: { result }, - } ); - result = null; - } - return result; -} - -export async function phpGetThemeDetails( - server: SiteServerProcess -): Promise< StartedSiteDetails[ 'themeDetails' ] > { - if ( ! server.php ) { - throw Error( 'PHP is not instantiated' ); - } - - const phpCode = ` - require_once('${ server.php.documentRoot }/wp-load.php'); - $theme = wp_get_theme(); - echo json_encode([ - 'name' => $theme->get('Name'), - 'uri' => $theme->get('ThemeURI'), - 'path' => $theme->get_stylesheet_directory(), - 'slug' => $theme->get_stylesheet(), - 'isBlockTheme' => $theme->is_block_theme(), - 'supportsWidgets' => current_theme_supports('widgets'), - 'supportsMenus' => get_registered_nav_menus() || current_theme_supports('menus'), - ]); - `; - - return runArbitraryPhpCode( server, phpCode ) as Promise< StartedSiteDetails[ 'themeDetails' ] >; -} - -export async function phpGetAllPlugins( server: SiteServerProcess ): Promise< unknown > { - if ( ! server.php ) { - throw Error( 'PHP is not instantiated' ); - } - const phpCode = ` - require_once('${ server.php.documentRoot }/wp-load.php'); - $plugins = get_plugins(); - echo json_encode($plugins); - `; - - return runArbitraryPhpCode( server, phpCode ); -} - -export async function phpGetAllThemes( server: SiteServerProcess ): Promise< unknown > { - if ( ! server.php ) { - throw Error( 'PHP is not instantiated' ); - } - const phpCode = ` - require_once('${ server.php.documentRoot }/wp-load.php'); - $themes = wp_get_themes(); - echo json_encode($themes); - `; - - return runArbitraryPhpCode( server, phpCode ); -} diff --git a/src/preload.ts b/src/preload.ts index e79b7263d..5cbe38334 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -3,6 +3,7 @@ import '@sentry/electron/preload'; import { contextBridge, ipcRenderer } from 'electron'; +import { WPNowOptions } from '../vendor/wp-now/src/config'; import { promptWindowsSpeedUpSites } from './lib/windows-helpers'; import type { LogLevel } from './logging'; @@ -35,12 +36,13 @@ const api: IpcApi = { ipcRenderer.invoke( 'generateProposedSitePath', siteName ), openLocalPath: ( path: string ) => ipcRenderer.invoke( 'openLocalPath', path ), getThemeDetails: ( id: string ) => ipcRenderer.invoke( 'getThemeDetails', id ), - getAllPlugins: ( id: string ) => ipcRenderer.invoke( 'getAllPlugins', id ), - getAllThemes: ( id: string ) => ipcRenderer.invoke( 'getAllThemes', id ), getThumbnailData: ( id: string ) => ipcRenderer.invoke( 'getThumbnailData', id ), getInstalledApps: () => ipcRenderer.invoke( 'getInstalledApps' ), - executeWPCLiInline: ( options: { projectPath: string; args: string[] } ) => - ipcRenderer.invoke( 'executeWPCLiInline', options ), + executeWPCLiInline: ( options: { + projectPath: string; + args: string[]; + forcedWPNowOptions?: WPNowOptions; + } ) => ipcRenderer.invoke( 'executeWPCLiInline', options ), getOnboardingData: () => ipcRenderer.invoke( 'getOnboardingData' ), saveOnboarding: ( onboardingCompleted: boolean ) => ipcRenderer.invoke( 'saveOnboarding', onboardingCompleted ), diff --git a/src/site-server.ts b/src/site-server.ts index b5e290a70..05f8e585a 100644 --- a/src/site-server.ts +++ b/src/site-server.ts @@ -6,7 +6,7 @@ import { WPNowMode } from '../vendor/wp-now/src/config'; import { getWordPressVersionPath } from '../vendor/wp-now/src/download'; import { pathExists, recursiveCopyDirectory, isEmptyDir } from './lib/fs-utils'; import { decodePassword } from './lib/passwords'; -import { phpGetThemeDetails } from './lib/php-get'; +import { phpGetThemeDetails } from './lib/php-get-theme-details'; import { portFinder } from './lib/port-finder'; import { sanitizeForLogging } from './lib/sanitize-for-logging'; import { getPreferredSiteLanguage } from './lib/site-language'; diff --git a/vendor/wp-now/src/execute-wp-cli.ts b/vendor/wp-now/src/execute-wp-cli.ts index 721715df6..32303d5a6 100644 --- a/vendor/wp-now/src/execute-wp-cli.ts +++ b/vendor/wp-now/src/execute-wp-cli.ts @@ -1,29 +1,27 @@ import startWPNow from './wp-now'; import { downloadWpCli } from './download'; import getWpCliPath from './get-wp-cli-path'; -import getWpNowConfig, { WPNowMode } from './config'; +import getWpNowConfig, { WPNowMode, WPNowOptions } from './config'; import { DEFAULT_PHP_VERSION, DEFAULT_WORDPRESS_VERSION } from './constants'; import { phpVar } from '@php-wasm/util'; /** * This is an unstable API. Multiple wp-cli commands may not work due to a current limitation on php-wasm and pthreads. */ -export async function executeWPCli ( projectPath: string, args: string[] ): Promise<{ stdout: string; stderr: string; }> { +export async function executeWPCli( projectPath: string, args: string[], forcedWPNowOptions?: WPNowOptions ): Promise<{ stdout: string; stderr: string; }> { await downloadWpCli(); - const options = await getWpNowConfig({ + let options = await getWpNowConfig({ php: DEFAULT_PHP_VERSION, wp: DEFAULT_WORDPRESS_VERSION, path: projectPath, }); - let mode = options.mode; - if (options.mode !== WPNowMode.WORDPRESS) { - // In case of not having the site projectPath, - // we use index mode to avoid other logic like downloading WordPress - mode = WPNowMode.INDEX; + options.mode = options.mode !== WPNowMode.WORDPRESS ? WPNowMode.INDEX : options.mode; + if (forcedWPNowOptions) { + options = { ...options, ...forcedWPNowOptions }; } - const { phpInstances, options: wpNowOptions } = await startWPNow({ + + const { phpInstances } = await startWPNow({ ...options, - mode, numberOfPhpInstances: 2, }); const [, php] = phpInstances; @@ -35,10 +33,10 @@ export async function executeWPCli ( projectPath: string, args: string[] ): Prom const stderrPath = '/tmp/stderr'; const wpCliPath = '/tmp/wp-cli.phar'; const runCliPath = '/tmp/run-cli.php'; - await php.writeFile(stderrPath, ''); + php.writeFile(stderrPath, ''); php.mount(getWpCliPath(), wpCliPath); - await php.writeFile( + php.writeFile( runCliPath, ` Date: Mon, 17 Jun 2024 12:38:27 +0300 Subject: [PATCH 3/7] Update: Include current sute php version in chat context --- src/hooks/use-chat-context.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hooks/use-chat-context.tsx b/src/hooks/use-chat-context.tsx index 51e953088..34499db75 100644 --- a/src/hooks/use-chat-context.tsx +++ b/src/hooks/use-chat-context.tsx @@ -7,6 +7,7 @@ import React, { useCallback, ReactNode, } from 'react'; +import { DEFAULT_PHP_VERSION } from '../../vendor/wp-now/src/constants'; import { getIpcApi } from '../lib/get-ipc-api'; import { useGetWpVersion } from './use-get-wp-version'; import { useSiteDetails } from './use-site-details'; @@ -140,16 +141,16 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { themeList: selectedSite?.id ? themesList[ selectedSite.id ] || [] : [], pluginList: selectedSite?.id ? pluginsList[ selectedSite.id ] || [] : [], wpVersion, - // This will be fetched by a hook when php selection is merged - phpVersion: '8.0', + phpVersion: selectedSite?.phpVersion ?? DEFAULT_PHP_VERSION, currentURL: `http://localhost:${ sitePort }`, themeName: siteThemeDetails?.name, isBlockTheme: siteThemeDetails?.isBlockTheme, }; }, [ numberOfSites, - themesList, selectedSite?.id, + selectedSite?.phpVersion, + themesList, pluginsList, wpVersion, sitePort, From 15474e6fe83fb4e1b26bba6492f51c8fbe367198 Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Tue, 18 Jun 2024 11:22:43 +0300 Subject: [PATCH 4/7] Update: Add more context to chat: OS, editors, site name --- src/hooks/use-assistant-api.ts | 3 +++ src/hooks/use-chat-context.tsx | 17 +++++++++++++++++ src/lib/is-installed.ts | 32 ++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/hooks/use-assistant-api.ts b/src/hooks/use-assistant-api.ts index 3d2ad637c..058f66a3d 100644 --- a/src/hooks/use-assistant-api.ts +++ b/src/hooks/use-assistant-api.ts @@ -17,6 +17,9 @@ const contextMapper = ( context?: ChatContextType ) => { themes: context.themeList, current_theme: context.themeName, is_block_theme: context.isBlockTheme, + ide: context.availableEditors, + site_name: context.siteName, + os: context.os, }; }; diff --git a/src/hooks/use-chat-context.tsx b/src/hooks/use-chat-context.tsx index 34499db75..8e9ed8de7 100644 --- a/src/hooks/use-chat-context.tsx +++ b/src/hooks/use-chat-context.tsx @@ -9,6 +9,7 @@ import React, { } from 'react'; import { DEFAULT_PHP_VERSION } from '../../vendor/wp-now/src/constants'; import { getIpcApi } from '../lib/get-ipc-api'; +import { useCheckInstalledApps } from './use-check-installed-apps'; import { useGetWpVersion } from './use-get-wp-version'; import { useSiteDetails } from './use-site-details'; import { useWindowListener } from './use-window-listener'; @@ -22,6 +23,9 @@ export interface ChatContextType { wpVersion: string; phpVersion: string; isBlockTheme?: boolean; + os: string; + availableEditors: string[]; + siteName?: string; } const ChatContext = createContext< ChatContextType >( { currentURL: '', @@ -32,6 +36,9 @@ const ChatContext = createContext< ChatContextType >( { phpVersion: '', isBlockTheme: false, wpVersion: '', + availableEditors: [] as string[], + os: '', + siteName: '', } ); interface ChatProviderProps { @@ -50,6 +57,7 @@ const parseWpCliOutput = ( stdout: string, defaultValue: string[] ): string[] => export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const [ initialLoad, setInitialLoad ] = useState< Record< string, boolean > >( {} ); + const installedApps = useCheckInstalledApps(); const { data: sites, loadingSites, selectedSite } = useSiteDetails(); const wpVersion = useGetWpVersion( selectedSite || ( {} as SiteDetails ) ); const [ pluginsList, setPluginsList ] = useState< Record< string, string[] > >( {} ); @@ -59,6 +67,10 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const sitePort = selectedSite?.port || ''; const siteThemeDetails = selectedSite?.themeDetails; + const availableEditors = Object.keys( installedApps ).filter( ( app ) => { + return installedApps[ app as keyof InstalledApps ]; + } ); + const fetchPluginList = useCallback( async ( path: string ) => { const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { projectPath: path, @@ -145,17 +157,22 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { currentURL: `http://localhost:${ sitePort }`, themeName: siteThemeDetails?.name, isBlockTheme: siteThemeDetails?.isBlockTheme, + availableEditors, + siteName: selectedSite?.name, + os: window.appGlobals.platform, }; }, [ numberOfSites, selectedSite?.id, selectedSite?.phpVersion, + selectedSite?.name, themesList, pluginsList, wpVersion, sitePort, siteThemeDetails?.name, siteThemeDetails?.isBlockTheme, + availableEditors, ] ); return { children }; diff --git a/src/lib/is-installed.ts b/src/lib/is-installed.ts index bfeda62ae..7258fea9b 100644 --- a/src/lib/is-installed.ts +++ b/src/lib/is-installed.ts @@ -2,16 +2,28 @@ import { app } from 'electron'; import fs from 'fs'; import path from 'path'; -const appPaths: Record< keyof InstalledApps, string > = - process.platform == 'win32' - ? { - vscode: path.join( app.getPath( 'appData' ), 'Code' ), - phpstorm: '', // Disable phpSotrm for Windows - } - : { - vscode: '/Applications/Visual Studio Code.app', - phpstorm: '/Applications/PhpStorm.app', - }; +let appPaths: Record< keyof InstalledApps, string >; + +if ( process.platform === 'darwin' ) { + appPaths = { + vscode: '/Applications/Visual Studio Code.app', + phpstorm: '/Applications/PhpStorm.app', + }; +} + +if ( process.platform === 'linux' ) { + appPaths = { + vscode: '/usr/bin/code', + phpstorm: '/usr/bin/phpstorm', + }; +} + +if ( process.platform === 'win32' ) { + appPaths = { + vscode: path.join( app.getPath( 'appData' ), 'Code' ), + phpstorm: '', // Disable phpStorm for Windows + }; +} export function isInstalled( key: keyof typeof appPaths ): boolean { if ( ! appPaths[ key ] ) { From 21ed77cf8c90cb1924f9d419fb6e862358c650bb Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Wed, 19 Jun 2024 10:44:23 +0300 Subject: [PATCH 5/7] Update: Fix parameter types and minor lint issues --- src/hooks/use-chat-context.tsx | 8 ++------ src/lib/cli.ts | 4 +++- src/lib/windows-helpers.ts | 2 +- src/preload.ts | 9 --------- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/hooks/use-chat-context.tsx b/src/hooks/use-chat-context.tsx index 8e9ed8de7..28101b9c9 100644 --- a/src/hooks/use-chat-context.tsx +++ b/src/hooks/use-chat-context.tsx @@ -74,9 +74,7 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const fetchPluginList = useCallback( async ( path: string ) => { const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { projectPath: path, - args: [ 'plugin', 'list', '--format=json', '--status=active' ], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - forcedWPNowOptions: { mode: 'index' as any }, + args: 'plugin list --format=json --status=active', } ); if ( stderr ) { return []; @@ -87,9 +85,7 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const fetchThemeList = useCallback( async ( path: string ) => { const { stdout, stderr } = await getIpcApi().executeWPCLiInline( { projectPath: path, - args: [ 'theme', 'list', '--format=json' ], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - forcedWPNowOptions: { mode: 'index' as any }, + args: 'theme list --format=json', } ); if ( stderr ) { return []; diff --git a/src/lib/cli.ts b/src/lib/cli.ts index edfce558a..bc9512e74 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -85,7 +85,9 @@ const setCommand = ( command: string ) => { const [ action, ...args ] = parse( command ); // The parsing of arguments can include shell operators like `>` or `||` that the app don't support. - const isValidCommand = args.every( ( arg ) => typeof arg === 'string' || arg instanceof String ); + const isValidCommand = args.every( + ( arg: unknown ) => typeof arg === 'string' || arg instanceof String + ); if ( ! isValidCommand ) { throw Error( `Can't execute command: ${ command }` ); } diff --git a/src/lib/windows-helpers.ts b/src/lib/windows-helpers.ts index f19976f08..c3f4b69af 100644 --- a/src/lib/windows-helpers.ts +++ b/src/lib/windows-helpers.ts @@ -1,4 +1,4 @@ -import { app, dialog, shell } from 'electron'; +import { app, dialog } from 'electron'; import path from 'path'; import { __ } from '@wordpress/i18n'; import sudo from 'sudo-prompt'; diff --git a/src/preload.ts b/src/preload.ts index 77bf1874b..0319c8d18 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -3,7 +3,6 @@ import '@sentry/electron/preload'; import { contextBridge, ipcRenderer } from 'electron'; -import { WPNowOptions } from '../vendor/wp-now/src/config'; import { promptWindowsSpeedUpSites } from './lib/windows-helpers'; import type { LogLevel } from './logging'; @@ -38,16 +37,8 @@ const api: IpcApi = { getThemeDetails: ( id: string ) => ipcRenderer.invoke( 'getThemeDetails', id ), getThumbnailData: ( id: string ) => ipcRenderer.invoke( 'getThumbnailData', id ), getInstalledApps: () => ipcRenderer.invoke( 'getInstalledApps' ), -<<<<<<< HEAD - executeWPCLiInline: ( options: { - projectPath: string; - args: string[]; - forcedWPNowOptions?: WPNowOptions; - } ) => ipcRenderer.invoke( 'executeWPCLiInline', options ), -======= executeWPCLiInline: ( options: { projectPath: string; args: string } ) => ipcRenderer.invoke( 'executeWPCLiInline', options ), ->>>>>>> trunk getOnboardingData: () => ipcRenderer.invoke( 'getOnboardingData' ), saveOnboarding: ( onboardingCompleted: boolean ) => ipcRenderer.invoke( 'saveOnboarding', onboardingCompleted ), From a958bf71a3062ccb058383706110a2041dc42686 Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Wed, 19 Jun 2024 13:12:40 +0300 Subject: [PATCH 6/7] Update: fix code based on comments --- src/hooks/use-chat-context.tsx | 17 +++++++++-------- src/lib/is-installed.ts | 8 ++------ vendor/wp-now/src/execute-wp-cli.ts | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/hooks/use-chat-context.tsx b/src/hooks/use-chat-context.tsx index 28101b9c9..8597b3835 100644 --- a/src/hooks/use-chat-context.tsx +++ b/src/hooks/use-chat-context.tsx @@ -13,6 +13,7 @@ import { useCheckInstalledApps } from './use-check-installed-apps'; import { useGetWpVersion } from './use-get-wp-version'; import { useSiteDetails } from './use-site-details'; import { useWindowListener } from './use-window-listener'; +import { useThemeDetails } from './use-theme-details'; export interface ChatContextType { currentURL: string; @@ -29,8 +30,8 @@ export interface ChatContextType { } const ChatContext = createContext< ChatContextType >( { currentURL: '', - pluginList: [] as string[], - themeList: [] as string[], + pluginList: [], + themeList: [], numberOfSites: 0, themeName: '', phpVersion: '', @@ -65,7 +66,8 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { const numberOfSites = sites?.length || 0; const sitePath = selectedSite?.path || ''; const sitePort = selectedSite?.port || ''; - const siteThemeDetails = selectedSite?.themeDetails; + + const { selectedThemeDetails: themeDetails } = useThemeDetails(); const availableEditors = Object.keys( installedApps ).filter( ( app ) => { return installedApps[ app as keyof InstalledApps ]; @@ -95,7 +97,6 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { useEffect( () => { let isCurrent = true; - // Initial load. Prefetch all the plugins and themes for the sites. const run = async () => { const result = await Promise.all( [ fetchPluginList( sitePath ), @@ -151,8 +152,8 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { wpVersion, phpVersion: selectedSite?.phpVersion ?? DEFAULT_PHP_VERSION, currentURL: `http://localhost:${ sitePort }`, - themeName: siteThemeDetails?.name, - isBlockTheme: siteThemeDetails?.isBlockTheme, + themeName: themeDetails?.name, + isBlockTheme: themeDetails?.isBlockTheme, availableEditors, siteName: selectedSite?.name, os: window.appGlobals.platform, @@ -166,8 +167,8 @@ export const ChatProvider: React.FC< ChatProviderProps > = ( { children } ) => { pluginsList, wpVersion, sitePort, - siteThemeDetails?.name, - siteThemeDetails?.isBlockTheme, + themeDetails?.name, + themeDetails?.isBlockTheme, availableEditors, ] ); diff --git a/src/lib/is-installed.ts b/src/lib/is-installed.ts index 7258fea9b..c4ede43f7 100644 --- a/src/lib/is-installed.ts +++ b/src/lib/is-installed.ts @@ -9,16 +9,12 @@ if ( process.platform === 'darwin' ) { vscode: '/Applications/Visual Studio Code.app', phpstorm: '/Applications/PhpStorm.app', }; -} - -if ( process.platform === 'linux' ) { +} else if ( process.platform === 'linux' ) { appPaths = { vscode: '/usr/bin/code', phpstorm: '/usr/bin/phpstorm', }; -} - -if ( process.platform === 'win32' ) { +} else if ( process.platform === 'win32' ) { appPaths = { vscode: path.join( app.getPath( 'appData' ), 'Code' ), phpstorm: '', // Disable phpStorm for Windows diff --git a/vendor/wp-now/src/execute-wp-cli.ts b/vendor/wp-now/src/execute-wp-cli.ts index c992b7a66..cb751ff6e 100644 --- a/vendor/wp-now/src/execute-wp-cli.ts +++ b/vendor/wp-now/src/execute-wp-cli.ts @@ -10,7 +10,7 @@ const isWindows = process.platform === 'win32'; /** * This is an unstable API. Multiple wp-cli commands may not work due to a current limitation on php-wasm and pthreads. */ -export async function executeWPCli( projectPath: string, args: string[], forcedWPNowOptions?: WPNowOptions ): Promise<{ stdout: string; stderr: string; }> { +export async function executeWPCli( projectPath: string, args: string[] ): Promise<{ stdout: string; stderr: string; }> { await downloadWpCli(); let options = await getWpNowConfig({ php: DEFAULT_PHP_VERSION, From 456f6826b6ef1ce5abd5188a45cc1e13f0d8393d Mon Sep 17 00:00:00 2001 From: Antony Agrios Date: Wed, 19 Jun 2024 13:17:22 +0300 Subject: [PATCH 7/7] Update: fix lint errors --- src/hooks/use-chat-context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/use-chat-context.tsx b/src/hooks/use-chat-context.tsx index 8597b3835..2668fadb8 100644 --- a/src/hooks/use-chat-context.tsx +++ b/src/hooks/use-chat-context.tsx @@ -12,8 +12,8 @@ import { getIpcApi } from '../lib/get-ipc-api'; import { useCheckInstalledApps } from './use-check-installed-apps'; import { useGetWpVersion } from './use-get-wp-version'; import { useSiteDetails } from './use-site-details'; -import { useWindowListener } from './use-window-listener'; import { useThemeDetails } from './use-theme-details'; +import { useWindowListener } from './use-window-listener'; export interface ChatContextType { currentURL: string;