diff --git a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt index 3526ae157b92ec..acb02972facde6 100644 --- a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt +++ b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt @@ -6,6 +6,7 @@ import expo.modules.audio.AudioModule import expo.modules.backgroundfetch.BackgroundFetchModule import expo.modules.backgroundtask.BackgroundTaskModule import expo.modules.battery.BatteryModule +import expo.modules.blob.BlobModule import expo.modules.blur.BlurModule import expo.modules.brightness.BrightnessModule import expo.modules.calendar.CalendarModule @@ -17,6 +18,7 @@ import expo.modules.constants.ConstantsService import expo.modules.contacts.ContactsModule import expo.modules.core.interfaces.Package import expo.modules.crypto.CryptoModule +import expo.modules.crypto.aes.AesCryptoModule import expo.modules.device.DeviceModule import expo.modules.documentpicker.DocumentPickerModule import expo.modules.easclient.EASClientModule @@ -131,9 +133,11 @@ object ExperiencePackagePicker : ModulesProvider { NotificationChannelGroupManagerModule::class.java to null, ExpoBackgroundNotificationTasksModule::class.java to null, // End of Notifications + AesCryptoModule::class.java to null, BatteryModule::class.java to null, BackgroundFetchModule::class.java to null, BackgroundTaskModule::class.java to null, + BlobModule::class.java to null, BlurModule::class.java to null, CalendarModule::class.java to null, CameraViewModule::class.java to null, diff --git a/apps/expo-go/android/settings.gradle b/apps/expo-go/android/settings.gradle index 7ec432689d24ba..9d2a00012493c7 100644 --- a/apps/expo-go/android/settings.gradle +++ b/apps/expo-go/android/settings.gradle @@ -40,7 +40,6 @@ expoAutolinking.exclude = [ 'expo-maps', 'expo-network-addons', 'expo-splash-screen', - 'expo-blob', '@expo/ui', 'expo-mesh-gradient', '@expo/app-integrity', diff --git a/apps/expo-go/ios/Podfile b/apps/expo-go/ios/Podfile index b142d1684ece57..73868fd2c099c2 100644 --- a/apps/expo-go/ios/Podfile +++ b/apps/expo-go/ios/Podfile @@ -50,7 +50,6 @@ target 'Expo Go' do 'expo-network-addons', 'expo-insights', 'expo-splash-screen', - 'expo-blob', '@expo/ui', '@expo/app-integrity', 'expo-brownfield' diff --git a/apps/native-component-list/src/screens/Image/ImageFormatsScreen.tsx b/apps/native-component-list/src/screens/Image/ImageFormatsScreen.tsx index ca71312eff8fd4..266593e875d8ff 100644 --- a/apps/native-component-list/src/screens/Image/ImageFormatsScreen.tsx +++ b/apps/native-component-list/src/screens/Image/ImageFormatsScreen.tsx @@ -121,6 +121,14 @@ const data: SectionListData[] = [ }, ], }, + Platform.OS === 'ios' && { + title: 'PSD', + data: [ + { + source: 'https://filesamples.com/samples/image/psd/sample_640%C3%97426.psd', + }, + ], + }, ].filter(Boolean) as SectionListData[]; function keyExtractor(item: any, index: number) { diff --git a/docs/pages/versions/unversioned/sdk/image.mdx b/docs/pages/versions/unversioned/sdk/image.mdx index 24902832bd2f45..948e8173d0e68f 100644 --- a/docs/pages/versions/unversioned/sdk/image.mdx +++ b/docs/pages/versions/unversioned/sdk/image.mdx @@ -32,17 +32,18 @@ import { Terminal } from '~/ui/components/Snippet'; #### Supported image formats -| Format | Android | iOS | Web | -| :--------: | :---------: | :---------: | :----------------------------------------------------: | -| WebP | | | | -| PNG / APNG | | | | -| AVIF | | | | -| HEIC | | | [not adopted yet](https://caniuse.com/heif) | -| JPEG | | | | -| GIF | | | | -| SVG | | | | -| ICO | | | | -| ICNS | | | | +| Format | Android | iOS | Web | +| :---------------------: | :---------: | :---------: | :----------------------------------------------------: | +| WebP | | | | +| PNG / APNG | | | | +| AVIF | | | | +| HEIC | | | [not adopted yet](https://caniuse.com/heif) | +| JPEG | | | | +| GIF | | | | +| SVG | | | | +| ICO | | | | +| ICNS | | | | +| PSD (composite preview) | | | | ## Installation diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index 372068c90d5db0..81683a258b0c2c 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -12,6 +12,7 @@ ### 🎉 New features - Switch EXPO_NO_GIT_STATUS to default to true. ([#42026](https://github.com/expo/expo/pull/42026) by [@EvanBacon](https://github.com/EvanBacon)) +- Added support for exposing cli command extensions as MCP tools. ([#40826](https://github.com/expo/expo/pull/40826) by [@chrfalch](https://github.com/chrfalch)) - Added support for cli command extension in expo modules ([#39598](https://github.com/expo/expo/pull/39598) by [@chrfalch](https://github.com/chrfalch)) - Add support for server data loaders in development ([#39570](https://github.com/expo/expo/pull/39570) by [@hassankhan](https://github.com/hassankhan)) - Added support for bundling apps with a new error overlay from `@expo/log-box` package ([#39958](https://github.com/expo/expo/pull/39958) by [@krystofwoldrich](https://github.com/krystofwoldrich)) diff --git a/packages/@expo/cli/package.json b/packages/@expo/cli/package.json index 336b2200e7232e..d8a4586f0dffcc 100644 --- a/packages/@expo/cli/package.json +++ b/packages/@expo/cli/package.json @@ -125,7 +125,7 @@ } }, "devDependencies": { - "@expo/mcp-tunnel": "~0.2.1", + "@expo/mcp-tunnel": "~0.2.3", "@expo/multipart-body-parser": "^1.0.0", "@expo/ngrok": "4.1.3", "@graphql-codegen/cli": "^2.16.3", diff --git a/packages/@expo/cli/src/start/interface/interactiveActions.ts b/packages/@expo/cli/src/start/interface/interactiveActions.ts index 989f2b3ed0116a..d66cf3dccc0a0f 100644 --- a/packages/@expo/cli/src/start/interface/interactiveActions.ts +++ b/packages/@expo/cli/src/start/interface/interactiveActions.ts @@ -148,7 +148,7 @@ export class DevServerManagerActions { const metroServerOrigin = this.devServerManager.getDefaultDevServer().getJsInspectorBaseUrl(); const plugins = await this.devServerManager.devtoolsPluginManager.queryPluginsAsync(); - Log.log(); + const menuItems = [ ...defaultMenuItems, ...createDevToolsMenuItems(plugins, defaultServerUrl, metroServerOrigin), diff --git a/packages/@expo/cli/src/start/server/DevToolsPluginCliExtensionExecutor.ts b/packages/@expo/cli/src/start/server/DevToolsPluginCliExtensionExecutor.ts index 9574b2d218d0a9..c13c8f5b70c944 100644 --- a/packages/@expo/cli/src/start/server/DevToolsPluginCliExtensionExecutor.ts +++ b/packages/@expo/cli/src/start/server/DevToolsPluginCliExtensionExecutor.ts @@ -83,7 +83,7 @@ export class DevToolsPluginCliExtensionExecutor { const tool = path.join(this.plugin.packageRoot, this.plugin.cliExtensions!.entryPoint); const child = this.spawnFunc( 'node', - [tool, command, `'${JSON.stringify(args)}'`, `'${metroServerOrigin}'`], + [tool, command, `${JSON.stringify(args)}`, `${metroServerOrigin}`], { cwd: this.projectRoot, env: { ...process.env }, diff --git a/packages/@expo/cli/src/start/server/MCPDevToolsPluginCLIExtensions.ts b/packages/@expo/cli/src/start/server/MCPDevToolsPluginCLIExtensions.ts new file mode 100644 index 00000000000000..dbab23d8375730 --- /dev/null +++ b/packages/@expo/cli/src/start/server/MCPDevToolsPluginCLIExtensions.ts @@ -0,0 +1,85 @@ +import { DevServerManager } from './DevServerManager'; +import { DevToolsPluginOutputSchema } from './DevToolsPlugin.schema'; +import { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor'; +import { McpServer } from './MCP'; +import { createMCPDevToolsExtensionSchema } from './createMCPDevToolsExtensionSchema'; +import { Log } from '../../log'; + +const debug = require('debug')('expo:start:server:devtools:mcp'); + +export async function addMcpCapabilities(mcpServer: McpServer, devServerManager: DevServerManager) { + const plugins = await devServerManager.devtoolsPluginManager.queryPluginsAsync(); + + for (const plugin of plugins) { + if (plugin.cliExtensions) { + const commands = (plugin.cliExtensions.commands ?? []).filter((p) => + p.environments?.includes('mcp') + ); + if (commands.length === 0) { + continue; + } + + const schema = createMCPDevToolsExtensionSchema(plugin); + + debug( + `Installing MCP CLI extension for plugin: ${plugin.packageName} - found ${commands.length} commands` + ); + + mcpServer.registerTool( + plugin.packageName, + { + title: plugin.packageName, + description: plugin.description, + inputSchema: { parameters: schema }, + }, + async ({ parameters }) => { + try { + const { command, ...args } = parameters; + + const metroServerOrigin = devServerManager + .getDefaultDevServer() + .getJsInspectorBaseUrl(); + + const results = await new DevToolsPluginCliExtensionExecutor( + plugin, + devServerManager.projectRoot + ).execute({ command, args, metroServerOrigin }); + + const parsedResults = DevToolsPluginOutputSchema.safeParse(results); + if (parsedResults.success === false) { + throw new Error( + `Invalid output from CLI command: ${parsedResults.error.issues + .map((issue) => issue.message) + .join(', ')}` + ); + } + return { + content: parsedResults.data + .map((line) => { + const { type } = line; + if (type === 'text') { + return { type, text: line.text, level: line.level, url: line.url }; + } else if (line.type === 'image' || line.type === 'audio') { + // We could present this as a resource_link, but it seems not to be well supported in MCP clients, + // so we'll return a text with the link instead. + return { + type: 'text', + text: `${type} resource: ${line.url}${line.text ? ' (' + line.text + ')' : ''}`, + } as const; + } + return null; + }) + .filter((line): line is Exclude => line !== null), + }; + } catch (e: any) { + Log.error('Error executing MCP CLI command:', e); + return { + content: [{ type: 'text', text: `Error executing command: ${e.toString()}` }], + isError: true, + }; + } + } + ); + } + } +} diff --git a/packages/@expo/cli/src/start/server/__tests__/MCPDevToolsPluginCLIExtensions-test.ts b/packages/@expo/cli/src/start/server/__tests__/MCPDevToolsPluginCLIExtensions-test.ts new file mode 100644 index 00000000000000..9fd1c63416c4af --- /dev/null +++ b/packages/@expo/cli/src/start/server/__tests__/MCPDevToolsPluginCLIExtensions-test.ts @@ -0,0 +1,238 @@ +import { Log } from '../../../log'; +import { DevServerManager } from '../DevServerManager'; +import { DevToolsPlugin } from '../DevToolsPlugin'; +import { DevToolsPluginCommand } from '../DevToolsPlugin.schema'; +import { DevToolsPluginCliExtensionExecutor } from '../DevToolsPluginCliExtensionExecutor'; +import { McpServer } from '../MCP'; +import { addMcpCapabilities } from '../MCPDevToolsPluginCLIExtensions'; + +jest.mock('../DevToolsPluginCliExtensionExecutor'); +jest.mock('../../../log', () => ({ + Log: { + error: jest.fn(), + log: jest.fn(), + }, +})); + +const MockedExecutor = DevToolsPluginCliExtensionExecutor as jest.MockedClass< + typeof DevToolsPluginCliExtensionExecutor +>; + +const PROJECT_ROOT = '/tmp/project'; + +let executeMock: jest.Mock; + +beforeEach(() => { + jest.clearAllMocks(); + executeMock = jest.fn(); + MockedExecutor.mockImplementation( + () => + ({ + execute: executeMock, + }) as unknown as InstanceType + ); +}); + +describe(addMcpCapabilities, () => { + it('registers MCP CLI commands as tools on the MCP server', async () => { + const mcpCommands: DevToolsPluginCommand[] = [ + createCommand({ + name: 'first-command', + parameters: [{ name: 'foo', type: 'text', description: 'Foo parameter' }], + }), + createCommand({ name: 'second-command' }), + ]; + const plugin = createPlugin('test-plugin', 'Test MCP plugin', mcpCommands); + + const { devServerManager, queryPluginsAsync } = createDevServerManager([plugin]); + const registerTool = jest.fn(); + const mcpServer = { registerTool } as unknown as McpServer; + + await addMcpCapabilities(mcpServer, devServerManager); + + expect(queryPluginsAsync).toHaveBeenCalledTimes(1); + expect(registerTool).toHaveBeenCalledTimes(1); + + const [toolName, toolDefinition, toolHandler] = registerTool.mock.calls[0]; + expect(toolName).toBe('test-plugin'); + expect(toolDefinition.title).toBe('test-plugin'); + expect(toolDefinition.description).toBe('Test MCP plugin'); + expect(typeof toolHandler).toBe('function'); + + const schema = toolDefinition.inputSchema.parameters; + expect(schema.safeParse({ command: 'first-command', foo: 'bar' }).success).toBe(true); + expect(schema.safeParse({ command: 'second-command' }).success).toBe(true); + expect(MockedExecutor).not.toHaveBeenCalled(); + }); + + it('executes registered command and formats output lines', async () => { + const command = createCommand({ + name: 'run-analysis', + parameters: [{ name: 'path', type: 'text', description: 'Target path' }], + }); + const plugin = createPlugin('analysis-plugin', 'Analysis Plugin', [command]); + const { devServerManager, getJsInspectorBaseUrl } = createDevServerManager( + [plugin], + 'http://localhost:19000' + ); + const registerTool = jest.fn(); + const mcpServer = { registerTool } as unknown as McpServer; + + const pluginOutput = [ + { type: 'text', text: 'Run complete', level: 'info', url: 'https://example.com' }, + { type: 'image', url: 'https://example.com/image.png', text: 'Screenshot' }, + { type: 'audio', url: 'https://example.com/sound.mp3' }, + ] as const; + executeMock.mockResolvedValue(pluginOutput); + + await addMcpCapabilities(mcpServer, devServerManager); + + const [, , handler] = registerTool.mock.calls[0]; + const result = await handler({ + parameters: { + command: 'run-analysis', + path: '/tmp/data', + }, + }); + + expect(MockedExecutor).toHaveBeenCalledTimes(1); + expect(MockedExecutor).toHaveBeenLastCalledWith(plugin, PROJECT_ROOT); + expect(executeMock).toHaveBeenCalledWith({ + command: 'run-analysis', + args: { path: '/tmp/data' }, + metroServerOrigin: 'http://localhost:19000', + }); + expect(getJsInspectorBaseUrl).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + content: [ + { + type: 'text', + text: 'Run complete', + level: 'info', + url: 'https://example.com', + }, + { + type: 'text', + text: 'image resource: https://example.com/image.png (Screenshot)', + }, + { + type: 'text', + text: 'audio resource: https://example.com/sound.mp3', + }, + ], + }); + }); + + it('returns error output when command execution fails', async () => { + const command = createCommand({ name: 'failing-command' }); + const plugin = createPlugin('broken-plugin', 'Broken Plugin', [command]); + const { devServerManager } = createDevServerManager([plugin]); + const registerTool = jest.fn(); + const mcpServer = { registerTool } as unknown as McpServer; + const error = new Error('Execution exploded'); + executeMock.mockRejectedValue(error); + + await addMcpCapabilities(mcpServer, devServerManager); + const [, , handler] = registerTool.mock.calls[0]; + const response = await handler({ + parameters: { command: 'failing-command' }, + }); + + const logError = Log.error as jest.Mock; + expect(logError).toHaveBeenCalledWith('Error executing MCP CLI command:', error); + expect(response).toEqual({ + content: [ + { + type: 'text', + text: 'Error executing command: Error: Execution exploded', + }, + ], + isError: true, + }); + }); + + it('skips plugins without MCP-compatible commands', async () => { + const cliOnlyCommand: DevToolsPluginCommand = { + name: 'local-only', + title: 'Local only', + environments: ['cli'], + parameters: [], + }; + const plugin = createPlugin('cli-plugin', 'CLI Plugin', [cliOnlyCommand]); + const pluginWithoutCliExtensions = new DevToolsPlugin( + { + packageName: 'ui-plugin', + packageRoot: '/packages/ui-plugin', + }, + PROJECT_ROOT + ); + + const { devServerManager, queryPluginsAsync } = createDevServerManager([ + plugin, + pluginWithoutCliExtensions, + ]); + const registerTool = jest.fn(); + const mcpServer = { registerTool } as unknown as McpServer; + + await addMcpCapabilities(mcpServer, devServerManager); + + expect(queryPluginsAsync).toHaveBeenCalledTimes(1); + expect(registerTool).not.toHaveBeenCalled(); + }); +}); + +function createPlugin( + packageName: string, + description: string, + commands: DevToolsPluginCommand[] +): DevToolsPlugin { + return new DevToolsPlugin( + { + packageName, + packageRoot: `/packages/${packageName}`, + cliExtensions: { + description, + entryPoint: 'dist/cli.js', + commands, + }, + }, + PROJECT_ROOT + ); +} + +function createCommand({ + name, + title = name, + parameters = [], +}: { + name: string; + title?: string; + parameters?: DevToolsPluginCommand['parameters']; +}): DevToolsPluginCommand { + return { + name, + title, + environments: ['mcp'], + parameters, + }; +} + +function createDevServerManager( + plugins: DevToolsPlugin[], + metroServerOrigin: string = 'http://localhost:8081' +): { + devServerManager: DevServerManager; + queryPluginsAsync: jest.Mock>; + getJsInspectorBaseUrl: jest.Mock; +} { + const queryPluginsAsync = jest.fn().mockResolvedValue(plugins); + const getJsInspectorBaseUrl = jest.fn().mockReturnValue(metroServerOrigin); + const defaultDevServer = { getJsInspectorBaseUrl }; + const devServerManager = { + projectRoot: PROJECT_ROOT, + devtoolsPluginManager: { queryPluginsAsync }, + getDefaultDevServer: jest.fn(() => defaultDevServer), + } as unknown as DevServerManager; + + return { devServerManager, queryPluginsAsync, getJsInspectorBaseUrl }; +} diff --git a/packages/@expo/cli/src/start/server/__tests__/createMCPDevToolsExtensionSchema-test.ts b/packages/@expo/cli/src/start/server/__tests__/createMCPDevToolsExtensionSchema-test.ts new file mode 100644 index 00000000000000..77d7adcfa70bdf --- /dev/null +++ b/packages/@expo/cli/src/start/server/__tests__/createMCPDevToolsExtensionSchema-test.ts @@ -0,0 +1,57 @@ +import { DevToolsPlugin } from '../DevToolsPlugin'; +import { DevToolsPluginInfo } from '../DevToolsPlugin.schema'; +import { createMCPDevToolsExtensionSchema } from '../createMCPDevToolsExtensionSchema'; + +describe(createMCPDevToolsExtensionSchema, () => { + const mockPlugin: DevToolsPluginInfo = { + packageName: 'mock-plugin', + packageRoot: '/path/to/mock-plugin', + cliExtensions: { + description: 'Expo: manage background tasks', + commands: [ + { name: 'list', title: 'List registered background tasks', environments: ['cli', 'mcp'] }, + { + name: 'task', + title: 'Show information about a registered background task', + environments: ['cli', 'mcp'], + parameters: [{ name: 'name', type: 'text', required: true }], + }, + { + name: 'trigger-test', + title: 'Trigger a test background task', + environments: ['cli', 'mcp'], + }, + ], + entryPoint: 'cli/build/index.js', + }, + } as DevToolsPluginInfo; + + it('generates correct schema for plugin with commands and parameters', () => { + const schema = createMCPDevToolsExtensionSchema(new DevToolsPlugin(mockPlugin, '')); + const parameters = { + command: 'task', + name: 'task 1', + }; + expect(schema.parse(parameters)).toEqual({ + command: 'task', + name: 'task 1', + }); + }); + + it('throws error if plugin has no commands', () => { + const pluginWithoutCommands: DevToolsPlugin = { + packageName: 'no-commands-plugin', + packageRoot: '/path/to/no-commands-plugin', + cliExtensions: { + entryPoint: 'cli-extension.js', + commands: [], + }, + } as DevToolsPlugin; + + expect(() => { + createMCPDevToolsExtensionSchema(pluginWithoutCommands); + }).toThrow( + 'Plugin no-commands-plugin has no commands defined. Please define at least one command.' + ); + }); +}); diff --git a/packages/@expo/cli/src/start/server/createMCPDevToolsExtensionSchema.ts b/packages/@expo/cli/src/start/server/createMCPDevToolsExtensionSchema.ts new file mode 100644 index 00000000000000..699269d49d193d --- /dev/null +++ b/packages/@expo/cli/src/start/server/createMCPDevToolsExtensionSchema.ts @@ -0,0 +1,109 @@ +import { z } from 'zod'; + +import { DevToolsPlugin } from './DevToolsPlugin'; + +/** + * Creates an MCP-compatible JSON schema for a DevTools plugin's CLI extensions. + * + * LLM agents have varying support for complex JSON schema features like `anyOf`/`oneOf` + * discriminated unions. This implementation uses a flat schema with an enum for the + * command name, which provides the best compatibility across different LLM providers: + * + * - OpenAI: Supports `anyOf` but requires `additionalProperties: false` and all fields `required` + * - Claude/Anthropic: Works best with simple flat schemas with enums + * - Other providers: Generally have limited or inconsistent support for discriminated unions + * + * To compensate for the lack of discriminated unions, parameter descriptions include + * which command(s) they belong to, and command descriptions include their parameters. + * + * The resulting schema structure is: + * ```json + * { + * "type": "object", + * "properties": { + * "command": { + * "type": "string", + * "enum": ["cmd1", "cmd2", ...], + * "description": "The command to execute. Available commands: \"cmd1\" - Title 1 (params: foo); ..." + * }, + * "foo": { "type": "string", "description": "Foo description (Used by: \"cmd1\")" }, + * ... + * }, + * "required": ["command"], + * "additionalProperties": false + * } + * ``` + */ +export function createMCPDevToolsExtensionSchema(plugin: DevToolsPlugin) { + if (plugin.cliExtensions == null || plugin.cliExtensions?.commands.length === 0) { + throw new Error( + `Plugin ${plugin.packageName} has no commands defined. Please define at least one command.` + ); + } + + const commands = plugin.cliExtensions.commands; + + // Build a rich description that explains each command and its parameters + const commandDescriptions = commands + .map((c) => { + const params = c.parameters?.map((p) => p.name).join(', '); + return params ? `"${c.name}": ${c.title} (params: ${params})` : `"${c.name}": ${c.title}`; + }) + .join('. '); + + // Create enum of command names for clear LLM selection + const commandNames = commands.map((c) => c.name) as [string, ...string[]]; + + // Collect all unique parameters across all commands + // Track which commands use each parameter for documentation + const parameterCommandMap: Record = {}; + const parameterDescriptions: Record = {}; + + for (const command of commands) { + if (command.parameters && command.parameters.length > 0) { + for (const param of command.parameters) { + if (!parameterCommandMap[param.name]) { + parameterCommandMap[param.name] = []; + parameterDescriptions[param.name] = param.description || ''; + } + parameterCommandMap[param.name].push(command.name); + } + } + } + + // Build parameters with descriptions that indicate which command(s) they belong to + const allParameters: Record = {}; + for (const [paramName, commandList] of Object.entries(parameterCommandMap)) { + const baseDescription = parameterDescriptions[paramName]; + const commandsUsingParam = + commandList.length === commands.length + ? 'all commands' + : commandList.map((c) => `"${c}"`).join(', '); + + // Include command context in the description so LLMs know when to use each parameter + const fullDescription = baseDescription + ? `${baseDescription} (Used by: ${commandsUsingParam})` + : `Parameter for: ${commandsUsingParam}`; + + allParameters[paramName] = z.string().optional().describe(fullDescription); + } + + // Build the command description with clear instructions for the LLM + const hasParameters = Object.keys(allParameters).length > 0; + const commandDescription = hasParameters + ? `Required. The command to execute. You must select exactly one command from the enum values. ` + + `Each command may require specific parameters - only include parameters that belong to the selected command. ` + + `Commands: ${commandDescriptions}.` + : `Required. The command to execute. Select exactly one from the available options. ` + + `Commands: ${commandDescriptions}.`; + + // Build the flat schema with additionalProperties: false for LLM compatibility + const schema = z + .object({ + command: z.enum(commandNames).describe(commandDescription), + ...allParameters, + }) + .strict(); // .strict() adds additionalProperties: false + + return schema; +} diff --git a/packages/@expo/cli/src/start/startAsync.ts b/packages/@expo/cli/src/start/startAsync.ts index 3bc9ce3fbd81e9..e92f061c7cf605 100644 --- a/packages/@expo/cli/src/start/startAsync.ts +++ b/packages/@expo/cli/src/start/startAsync.ts @@ -16,6 +16,9 @@ import { env } from '../utils/env'; import { isInteractive } from '../utils/interactive'; import { profile } from '../utils/profile'; import { maybeCreateMCPServerAsync } from './server/MCP'; +import { addMcpCapabilities } from './server/MCPDevToolsPluginCLIExtensions'; + +const debug = require('debug')('expo:start'); async function getMultiBundlerStartOptions( projectRoot: string, @@ -136,7 +139,11 @@ export async function startAsync( Log.log(chalk`Waiting on {underline ${defaultServerUrl}}`); } } - mcpServer?.start(); + + if (mcpServer) { + addMcpCapabilities(mcpServer, devServerManager); + mcpServer.start(); + } // Final note about closing the server. const logLocation = settings.webOnly ? 'in the browser console' : 'below'; diff --git a/packages/expo-image/CHANGELOG.md b/packages/expo-image/CHANGELOG.md index 91892d6428dc6e..474beac258f0eb 100644 --- a/packages/expo-image/CHANGELOG.md +++ b/packages/expo-image/CHANGELOG.md @@ -12,6 +12,7 @@ - [iOS] Provide plugin to disable `libdav1d`. ([#40691](https://github.com/expo/expo/pull/40691) by [@alanjhughes](https://github.com/alanjhughes)) - [iOS] feat: add `configureCache` option ([#40647](https://github.com/expo/expo/pull/40647) by [@kosmydel](https://github.com/kosmydel)) - [Web] Add `loading` prop for lazy loading images. ([#41442](https://github.com/expo/expo/pull/41442) by [@mozzius](https://github.com/mozzius)) +- [iOS] Added support for PSD images. ([#42077](https://github.com/expo/expo/pull/42077) by [@barthap](https://github.com/barthap)) ### 🐛 Bug fixes diff --git a/packages/expo-image/ios/ImageModule.swift b/packages/expo-image/ios/ImageModule.swift index 65070e9b862298..bf3975fc107026 100644 --- a/packages/expo-image/ios/ImageModule.swift +++ b/packages/expo-image/ios/ImageModule.swift @@ -261,6 +261,7 @@ public final class ImageModule: Module { static func registerCoders() { SDImageCodersManager.shared.addCoder(WebPCoder.shared) + SDImageCodersManager.shared.addCoder(PSDCoder.shared) SDImageCodersManager.shared.addCoder(SDImageAVIFCoder.shared) SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared) SDImageCodersManager.shared.addCoder(SDImageHEICCoder.shared) diff --git a/packages/expo-image/ios/PSDCoder.swift b/packages/expo-image/ios/PSDCoder.swift new file mode 100644 index 00000000000000..a7b18155032ecd --- /dev/null +++ b/packages/expo-image/ios/PSDCoder.swift @@ -0,0 +1,39 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import SDWebImage + +internal final class PSDCoder: NSObject, SDImageCoder { + nonisolated(unsafe) static let shared = PSDCoder() + + func canDecode(from data: Data?) -> Bool { + guard let data, data.count >= 4 else { + return false + } + + // verify PSD magic bytes + let signatureData = data[0..<4] + let signature = String(data: signatureData, encoding: .ascii) + return signature == "8BPS" + } + + func decodedImage(with data: Data?, options: [SDImageCoderOption : Any]? = nil) -> UIImage? { + guard let data else { + return nil + } + + if let scale = options?[SDImageCoderOption.decodeScaleFactor] as? CGFloat { + return UIImage(data: data, scale: scale) + } + + // UIImage is able to directly handle PSD data + return UIImage(data: data) + } + + func canEncode(to format: SDImageFormat) -> Bool { + return false + } + + func encodedData(with image: UIImage?, format: SDImageFormat, options: [SDImageCoderOption : Any]? = nil) -> Data? { + return nil + } +} diff --git a/yarn.lock b/yarn.lock index 590928a0a4ef37..655e58e67567bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1575,11 +1575,12 @@ "@expo/sudo-prompt" "^9.3.1" debug "^3.1.0" -"@expo/mcp-tunnel@~0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@expo/mcp-tunnel/-/mcp-tunnel-0.2.1.tgz#ed392d86702e44f9ef22cff653d2cdc0c7c575b3" - integrity sha512-2cuTYx4Ewe5UzQgtKd0FZWX8+es9c+y26YmT4fqhgw4q59uLuZjDZlTnJtsq3NMMTMmv+3Xl83IbvcqYoBd3oA== +"@expo/mcp-tunnel@~0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@expo/mcp-tunnel/-/mcp-tunnel-0.2.3.tgz#349b9da026b172d0b04edede14e982b4deaa437b" + integrity sha512-AUyjAFxrDASPZo95BO8iUi8SqDXi97K9toCl/aT1vAU/1Ap5W7K+1IrMWNJivFdT7/XHvXU5LmoRtg5vXpYc1Q== dependencies: + "@modelcontextprotocol/sdk" "^1.17.5" ws "^8.18.3" zod "^3.25.76" zod-to-json-schema "^3.24.6" @@ -2652,6 +2653,11 @@ protobufjs "^6.10.0" yargs "^16.2.0" +"@hono/node-server@^1.19.7": + version "1.19.7" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.7.tgz#ecb2d3a7af40d1d378e53ce1fc1219f199fbcd6f" + integrity sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -3139,6 +3145,28 @@ dependencies: lottie-web "^5.12.2" +"@modelcontextprotocol/sdk@^1.17.5": + version "1.25.2" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz#2284560b4e044b4ce5f328ee180931110cb8c5cf" + integrity sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww== + dependencies: + "@hono/node-server" "^1.19.7" + ajv "^8.17.1" + ajv-formats "^3.0.1" + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.5" + eventsource "^3.0.2" + eventsource-parser "^3.0.0" + express "^5.0.1" + express-rate-limit "^7.5.0" + jose "^6.1.1" + json-schema-typed "^8.0.2" + pkce-challenge "^5.0.0" + raw-body "^3.0.0" + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.0" + "@mswjs/interceptors@^0.39.5": version "0.39.8" resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.39.8.tgz#0a2cf4cf26a731214ca4156273121f67dff7ebf8" @@ -5238,6 +5266,13 @@ ajv-formats@^2.0.2: dependencies: ajv "^8.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv@^6.12.4, ajv@^6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -5248,15 +5283,15 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.1.0, ajv@^8.11.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== +ajv@^8.0.0, ajv@^8.1.0, ajv@^8.11.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" @@ -5907,20 +5942,20 @@ body-parser@1.20.3: type-is "~1.6.18" unpipe "1.0.0" -body-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" - integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== +body-parser@^2.2.0, body-parser@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== dependencies: bytes "^3.1.2" content-type "^1.0.5" - debug "^4.4.0" + debug "^4.4.3" http-errors "^2.0.0" - iconv-lite "^0.6.3" + iconv-lite "^0.7.0" on-finished "^2.4.1" - qs "^6.14.0" - raw-body "^3.0.0" - type-is "^2.0.0" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" boolbase@^1.0.0: version "1.0.0" @@ -6040,7 +6075,7 @@ busboy@^1.6.0: dependencies: streamsearch "^1.1.0" -bytes@3.1.2, bytes@^3.1.2: +bytes@3.1.2, bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -6722,6 +6757,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig-typescript-loader@^4.3.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz#f3feae459ea090f131df5474ce4b1222912319f9" @@ -7020,10 +7063,10 @@ debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -8093,6 +8136,18 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventsource-parser@^3.0.0, eventsource-parser@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + +eventsource@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" + integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + dependencies: + eventsource-parser "^3.0.1" + exec-async@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/exec-async/-/exec-async-2.2.0.tgz#c7c5ad2eef3478d38390c6dd3acfe8af0efc8301" @@ -8234,6 +8289,11 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +express-rate-limit@^7.5.0: + version "7.5.1" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.1.tgz#8c3a42f69209a3a1c969890070ece9e20a879dec" + integrity sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw== + express@^4.19.2: version "4.21.1" resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281" @@ -8271,18 +8331,19 @@ express@^4.19.2: utils-merge "1.0.1" vary "~1.1.2" -express@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" - integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== +express@^5.0.1, express@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== dependencies: accepts "^2.0.0" - body-parser "^2.2.0" + body-parser "^2.2.1" content-disposition "^1.0.0" content-type "^1.0.5" cookie "^0.7.1" cookie-signature "^1.2.1" debug "^4.4.0" + depd "^2.0.0" encodeurl "^2.0.0" escape-html "^1.0.3" etag "^1.8.1" @@ -8402,6 +8463,11 @@ fast-querystring@^1.1.1: dependencies: fast-decode-uri-component "^1.0.1" +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fast-url-parser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" @@ -9355,7 +9421,7 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== -http-errors@2.0.0, http-errors@^2.0.0: +http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== @@ -9366,6 +9432,17 @@ http-errors@2.0.0, http-errors@^2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-errors@^2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + http-parser-js@>=0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" @@ -9453,6 +9530,13 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.1.tgz#d4af1d2092f2bb05aab6296e5e7cd286d2f15432" + integrity sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -9549,7 +9633,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -10666,6 +10750,11 @@ jose@^5: resolved "https://registry.yarnpkg.com/jose/-/jose-5.10.0.tgz#c37346a099d6467c401351a9a0c2161e0f52c4be" integrity sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg== +jose@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" + integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -10778,6 +10867,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema-typed@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz#e98ee7b1899ff4a184534d1f167c288c66bbeff4" + integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -12162,7 +12256,7 @@ ob1@0.83.3: dependencies: flow-enums-runtime "^0.0.6" -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -12709,6 +12803,11 @@ pixi.js@^4.6.1: remove-array-items "^1.0.0" resource-loader "^2.2.3" +pkce-challenge@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz#3b4446865b17b1745e9ace2016a31f48ddf6230d" + integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== + pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -13096,10 +13195,10 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" -qs@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== +qs@^6.14.0, qs@^6.14.1: + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== dependencies: side-channel "^1.1.0" @@ -13169,15 +13268,15 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" - integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== +raw-body@^3.0.0, raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.6.3" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@~1.2.7: version "1.2.8" @@ -14327,7 +14426,7 @@ setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -setprototypeof@1.2.0: +setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -14766,7 +14865,7 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -statuses@^2.0.1: +statuses@^2.0.1, statuses@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== @@ -15330,7 +15429,7 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toidentifier@1.0.1: +toidentifier@1.0.1, toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== @@ -15927,7 +16026,7 @@ value-or-promise@1.0.12, value-or-promise@^1.0.11, value-or-promise@^1.0.12: resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c" integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== -vary@^1.1.2, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -16774,10 +16873,15 @@ zen-observable@0.8.15: resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== -zod-to-json-schema@^3.24.6: - version "3.24.6" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz#5920f020c4d2647edfbb954fa036082b92c9e12d" - integrity sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg== +zod-to-json-schema@^3.24.6, zod-to-json-schema@^3.25.0: + version "3.25.1" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz#7f24962101a439ddade2bf1aeab3c3bfec7d84ba" + integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== + +"zod@^3.25 || ^4.0": + version "4.3.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g== zod@^3.25.76: version "3.25.76"