@apollo/client-ai-apps 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +1 -0
- package/.github/workflows/pr.yaml +23 -1
- package/.github/workflows/release.yaml +1 -5
- package/.github/workflows/verify-changeset.yml +112 -21
- package/CHANGELOG.md +361 -0
- package/__mocks__/fs/promises.cjs +3 -0
- package/__mocks__/fs.cjs +3 -0
- package/dist/config/defineConfig.d.ts +28 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +7 -0
- package/dist/config/defineConfig.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +29 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +51 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/core/ApolloClient.d.ts +4 -3
- package/dist/core/ApolloClient.d.ts.map +1 -1
- package/dist/core/ApolloClient.js +6 -59
- package/dist/core/ApolloClient.js.map +1 -1
- package/dist/core/Platform.d.ts +8 -0
- package/dist/core/Platform.d.ts.map +1 -0
- package/dist/core/Platform.js +20 -0
- package/dist/core/Platform.js.map +1 -0
- package/dist/core/types.d.ts +13 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +1 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mcp.d.ts +5 -0
- package/dist/index.mcp.d.ts.map +1 -0
- package/dist/index.mcp.js +5 -0
- package/dist/index.mcp.js.map +1 -0
- package/dist/index.openai.d.ts +5 -0
- package/dist/index.openai.d.ts.map +1 -0
- package/dist/index.openai.js +5 -0
- package/dist/index.openai.js.map +1 -0
- package/dist/link/ToolCallLink.d.ts +3 -3
- package/dist/link/ToolCallLink.d.ts.map +1 -1
- package/dist/link/ToolCallLink.js +5 -14
- package/dist/link/ToolCallLink.js.map +1 -1
- package/dist/mcp/core/ApolloClient.d.ts +21 -0
- package/dist/mcp/core/ApolloClient.d.ts.map +1 -0
- package/dist/mcp/core/ApolloClient.js +65 -0
- package/dist/mcp/core/ApolloClient.js.map +1 -0
- package/dist/mcp/core/McpAppManager.d.ts +34 -0
- package/dist/mcp/core/McpAppManager.d.ts.map +1 -0
- package/dist/mcp/core/McpAppManager.js +63 -0
- package/dist/mcp/core/McpAppManager.js.map +1 -0
- package/dist/mcp/core/Platform.d.ts +8 -0
- package/dist/mcp/core/Platform.d.ts.map +1 -0
- package/dist/mcp/core/Platform.js +8 -0
- package/dist/mcp/core/Platform.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/link/ToolCallLink.d.ts +28 -0
- package/dist/mcp/link/ToolCallLink.d.ts.map +1 -0
- package/dist/mcp/link/ToolCallLink.js +35 -0
- package/dist/mcp/link/ToolCallLink.js.map +1 -0
- package/dist/mcp/react/hooks/useApolloClient.d.ts +3 -0
- package/dist/mcp/react/hooks/useApolloClient.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useApolloClient.js +9 -0
- package/dist/mcp/react/hooks/useApolloClient.js.map +1 -0
- package/dist/mcp/react/hooks/useApp.d.ts +2 -0
- package/dist/mcp/react/hooks/useApp.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useApp.js +5 -0
- package/dist/mcp/react/hooks/useApp.js.map +1 -0
- package/dist/mcp/react/hooks/useToolInput.d.ts +2 -0
- package/dist/mcp/react/hooks/useToolInput.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useToolInput.js +5 -0
- package/dist/mcp/react/hooks/useToolInput.js.map +1 -0
- package/dist/mcp/react/hooks/useToolMetadata.d.ts +8 -0
- package/dist/mcp/react/hooks/useToolMetadata.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useToolMetadata.js +5 -0
- package/dist/mcp/react/hooks/useToolMetadata.js.map +1 -0
- package/dist/mcp/react/hooks/useToolName.d.ts +2 -0
- package/dist/mcp/react/hooks/useToolName.d.ts.map +1 -0
- package/dist/mcp/react/hooks/useToolName.js +5 -0
- package/dist/mcp/react/hooks/useToolName.js.map +1 -0
- package/dist/mcp/react/index.d.ts +5 -0
- package/dist/mcp/react/index.d.ts.map +1 -0
- package/dist/mcp/react/index.js +5 -0
- package/dist/mcp/react/index.js.map +1 -0
- package/dist/openai/core/ApolloClient.d.ts +21 -0
- package/dist/openai/core/ApolloClient.d.ts.map +1 -0
- package/dist/openai/core/ApolloClient.js +66 -0
- package/dist/openai/core/ApolloClient.js.map +1 -0
- package/dist/openai/core/McpAppManager.d.ts +28 -0
- package/dist/openai/core/McpAppManager.d.ts.map +1 -0
- package/dist/openai/core/McpAppManager.js +73 -0
- package/dist/openai/core/McpAppManager.js.map +1 -0
- package/dist/openai/core/Platform.d.ts +8 -0
- package/dist/openai/core/Platform.d.ts.map +1 -0
- package/dist/openai/core/Platform.js +8 -0
- package/dist/openai/core/Platform.js.map +1 -0
- package/dist/openai/globals.d.ts +12 -0
- package/dist/openai/globals.d.ts.map +1 -0
- package/dist/openai/globals.js +2 -0
- package/dist/openai/globals.js.map +1 -0
- package/dist/openai/index.d.ts +3 -0
- package/dist/openai/index.d.ts.map +1 -0
- package/dist/openai/index.js +3 -0
- package/dist/openai/index.js.map +1 -0
- package/dist/openai/link/ToolCallLink.d.ts +28 -0
- package/dist/openai/link/ToolCallLink.d.ts.map +1 -0
- package/dist/openai/link/ToolCallLink.js +35 -0
- package/dist/openai/link/ToolCallLink.js.map +1 -0
- package/dist/openai/react/hooks/useApolloClient.d.ts +3 -0
- package/dist/openai/react/hooks/useApolloClient.d.ts.map +1 -0
- package/dist/openai/react/hooks/useApolloClient.js +9 -0
- package/dist/openai/react/hooks/useApolloClient.js.map +1 -0
- package/dist/openai/react/hooks/useApp.d.ts +2 -0
- package/dist/openai/react/hooks/useApp.d.ts.map +1 -0
- package/dist/openai/react/hooks/useApp.js +5 -0
- package/dist/openai/react/hooks/useApp.js.map +1 -0
- package/dist/openai/react/hooks/useOpenAiGlobal.d.ts +3 -0
- package/dist/openai/react/hooks/useOpenAiGlobal.d.ts.map +1 -0
- package/dist/{react → openai/react}/hooks/useOpenAiGlobal.js +1 -1
- package/dist/openai/react/hooks/useOpenAiGlobal.js.map +1 -0
- package/dist/openai/react/hooks/useToolInput.d.ts +2 -0
- package/dist/openai/react/hooks/useToolInput.d.ts.map +1 -0
- package/dist/openai/react/hooks/useToolInput.js +5 -0
- package/dist/openai/react/hooks/useToolInput.js.map +1 -0
- package/dist/openai/react/hooks/useToolMetadata.d.ts +2 -0
- package/dist/openai/react/hooks/useToolMetadata.d.ts.map +1 -0
- package/dist/openai/react/hooks/useToolMetadata.js +5 -0
- package/dist/openai/react/hooks/useToolMetadata.js.map +1 -0
- package/dist/openai/react/hooks/useToolName.d.ts.map +1 -0
- package/dist/openai/react/hooks/useToolName.js +5 -0
- package/dist/openai/react/hooks/useToolName.js.map +1 -0
- package/dist/{react → openai/react}/hooks/useWidgetState.d.ts +1 -1
- package/dist/openai/react/hooks/useWidgetState.d.ts.map +1 -0
- package/dist/openai/react/hooks/useWidgetState.js.map +1 -0
- package/dist/openai/react/index.d.ts +6 -0
- package/dist/openai/react/index.d.ts.map +1 -0
- package/dist/openai/react/index.js +6 -0
- package/dist/openai/react/index.js.map +1 -0
- package/dist/{types/openai.d.ts → openai/types.d.ts} +7 -11
- package/dist/openai/types.d.ts.map +1 -0
- package/dist/{types/openai.js → openai/types.js} +1 -1
- package/dist/openai/types.js.map +1 -0
- package/dist/react/ApolloProvider.d.ts +6 -2
- package/dist/react/ApolloProvider.d.ts.map +1 -1
- package/dist/react/ApolloProvider.js +12 -27
- package/dist/react/ApolloProvider.js.map +1 -1
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +9 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mcp.d.ts +3 -0
- package/dist/react/index.mcp.d.ts.map +1 -0
- package/dist/react/index.mcp.js +3 -0
- package/dist/react/index.mcp.js.map +1 -0
- package/dist/react/index.openai.d.ts +3 -0
- package/dist/react/index.openai.d.ts.map +1 -0
- package/dist/react/index.openai.js +3 -0
- package/dist/react/index.openai.js.map +1 -0
- package/dist/react/missingHook.d.ts +2 -0
- package/dist/react/missingHook.d.ts.map +1 -0
- package/dist/react/missingHook.js +6 -0
- package/dist/react/missingHook.js.map +1 -0
- package/dist/tsconfig/core/tsconfig.json +5 -0
- package/dist/tsconfig/mcp/tsconfig.json +6 -0
- package/dist/tsconfig/openai/tsconfig.json +7 -0
- package/dist/types/application-manifest.d.ts +5 -1
- package/dist/types/application-manifest.d.ts.map +1 -1
- package/dist/types/application-manifest.js.map +1 -1
- package/dist/utilities/cacheAsync.d.ts +4 -0
- package/dist/utilities/cacheAsync.d.ts.map +1 -0
- package/dist/utilities/cacheAsync.js +19 -0
- package/dist/utilities/cacheAsync.js.map +1 -0
- package/dist/utilities/constants.d.ts +6 -0
- package/dist/utilities/constants.d.ts.map +1 -0
- package/dist/utilities/constants.js +6 -0
- package/dist/utilities/constants.js.map +1 -0
- package/dist/utilities/emptyModule.d.ts +2 -0
- package/dist/utilities/emptyModule.d.ts.map +1 -0
- package/dist/utilities/emptyModule.js +2 -0
- package/dist/utilities/emptyModule.js.map +1 -0
- package/dist/utilities/getVariablesForOperationFromToolInput.d.ts +5 -0
- package/dist/utilities/getVariablesForOperationFromToolInput.d.ts.map +1 -0
- package/dist/utilities/getVariablesForOperationFromToolInput.js +18 -0
- package/dist/utilities/getVariablesForOperationFromToolInput.js.map +1 -0
- package/dist/utilities/index.d.ts +6 -0
- package/dist/utilities/index.d.ts.map +1 -0
- package/dist/utilities/index.js +6 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/invariant.d.ts +2 -0
- package/dist/utilities/invariant.d.ts.map +1 -0
- package/dist/utilities/invariant.js +6 -0
- package/dist/utilities/invariant.js.map +1 -0
- package/dist/utilities/promiseWithResolvers.d.ts +7 -0
- package/dist/utilities/promiseWithResolvers.d.ts.map +1 -0
- package/dist/utilities/promiseWithResolvers.js +16 -0
- package/dist/utilities/promiseWithResolvers.js.map +1 -0
- package/dist/vite/__tests__/utilities/build.d.ts +41 -0
- package/dist/vite/__tests__/utilities/build.d.ts.map +1 -0
- package/dist/vite/__tests__/utilities/build.js +63 -0
- package/dist/vite/__tests__/utilities/build.js.map +1 -0
- package/dist/vite/apolloClientAiApps.d.ts +14 -0
- package/dist/vite/apolloClientAiApps.d.ts.map +1 -0
- package/dist/vite/apolloClientAiApps.js +350 -0
- package/dist/vite/apolloClientAiApps.js.map +1 -0
- package/dist/vite/index.d.ts +1 -2
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +1 -2
- package/dist/vite/index.js.map +1 -1
- package/dist/vite/utilities/config.d.ts +2 -0
- package/dist/vite/utilities/config.d.ts.map +1 -0
- package/dist/vite/utilities/config.js +11 -0
- package/dist/vite/utilities/config.js.map +1 -0
- package/dist/vite/utilities/graphql.d.ts +20 -0
- package/dist/vite/utilities/graphql.d.ts.map +1 -0
- package/dist/vite/utilities/graphql.js +42 -0
- package/dist/vite/utilities/graphql.js.map +1 -0
- package/knope.toml +1 -1
- package/package.json +105 -9
- package/src/config/defineConfig.ts +8 -0
- package/src/config/index.ts +2 -0
- package/src/config/schema.ts +73 -0
- package/src/config/types.ts +7 -0
- package/src/core/ApolloClient.ts +7 -95
- package/src/core/Platform.ts +27 -0
- package/src/core/types.ts +14 -0
- package/src/index.mcp.ts +5 -0
- package/src/index.openai.ts +5 -0
- package/src/index.ts +2 -27
- package/src/link/ToolCallLink.ts +6 -22
- package/src/mcp/core/ApolloClient.ts +102 -0
- package/src/mcp/core/McpAppManager.ts +98 -0
- package/src/mcp/core/Platform.ts +11 -0
- package/src/mcp/core/__tests__/ApolloClient.test.ts +464 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/link/ToolCallLink.ts +40 -0
- package/src/mcp/link/__tests__/ToolCallLink.test.ts +53 -0
- package/src/mcp/react/hooks/__tests__/useApp.test.tsx +46 -0
- package/src/mcp/react/hooks/__tests__/useToolInput.test.tsx +50 -0
- package/src/mcp/react/hooks/__tests__/useToolMetadata.test.tsx +53 -0
- package/src/mcp/react/hooks/__tests__/useToolName.test.tsx +50 -0
- package/src/mcp/react/hooks/useApolloClient.ts +14 -0
- package/src/mcp/react/hooks/useApp.ts +5 -0
- package/src/mcp/react/hooks/useToolInput.ts +5 -0
- package/src/mcp/react/hooks/useToolMetadata.ts +5 -0
- package/src/mcp/react/hooks/useToolName.ts +5 -0
- package/src/mcp/react/index.ts +4 -0
- package/src/openai/core/ApolloClient.ts +100 -0
- package/src/openai/core/McpAppManager.ts +110 -0
- package/src/openai/core/Platform.ts +11 -0
- package/src/openai/core/__tests__/ApolloClient.test.ts +537 -0
- package/src/openai/globals.ts +14 -0
- package/src/openai/index.ts +2 -0
- package/src/openai/link/ToolCallLink.ts +40 -0
- package/src/{react → openai/react}/hooks/__tests__/useOpenAiGlobal.test.ts +1 -1
- package/src/openai/react/hooks/__tests__/useToolInput.test.tsx +85 -0
- package/src/openai/react/hooks/__tests__/useToolMetadata.test.tsx +86 -0
- package/src/openai/react/hooks/__tests__/useToolName.test.tsx +50 -0
- package/src/{react → openai/react}/hooks/__tests__/useWidgetState.test.tsx +1 -1
- package/src/openai/react/hooks/useApolloClient.ts +14 -0
- package/src/openai/react/hooks/useApp.ts +5 -0
- package/src/{react → openai/react}/hooks/useOpenAiGlobal.ts +2 -2
- package/src/openai/react/hooks/useToolInput.ts +5 -0
- package/src/openai/react/hooks/useToolMetadata.ts +5 -0
- package/src/openai/react/hooks/useToolName.ts +5 -0
- package/src/{react → openai/react}/hooks/useWidgetState.ts +1 -1
- package/src/openai/react/index.ts +5 -0
- package/src/{types/openai.ts → openai/types.ts} +9 -13
- package/src/react/ApolloProvider.tsx +23 -31
- package/src/react/__tests__/ApolloProvider/mcp.test.tsx +74 -0
- package/src/react/__tests__/ApolloProvider/openai.test.tsx +146 -0
- package/src/react/index.mcp.ts +7 -0
- package/src/react/index.openai.ts +7 -0
- package/src/react/index.ts +19 -0
- package/src/react/missingHook.ts +9 -0
- package/src/testing/internal/index.ts +8 -0
- package/src/testing/internal/matchers/index.ts +2 -0
- package/src/testing/internal/matchers/toComplete.ts +46 -0
- package/src/testing/internal/matchers/toEmitValue.ts +71 -0
- package/src/testing/internal/matchers/types.ts +3 -0
- package/src/testing/internal/mcp/graphqlToolResult.ts +31 -0
- package/src/testing/internal/mcp/minimalHostContextWithToolName.ts +9 -0
- package/src/testing/internal/mcp/mockMcpHost.ts +188 -0
- package/src/testing/internal/openai/dispatchStateChange.ts +1 -1
- package/src/testing/internal/openai/stubOpenAiGlobals.ts +22 -9
- package/src/testing/internal/react/renderAsync.ts +7 -0
- package/src/testing/internal/utilities/ObservableStream.ts +172 -0
- package/src/testing/internal/utilities/getSerializableProperties.ts +55 -0
- package/src/testing/internal/utilities/mockApplicationManifest.ts +39 -0
- package/src/testing/internal/utilities/spyOnConsole.ts +29 -0
- package/src/testing/internal/utilities/wait.ts +3 -0
- package/src/tsconfig/core/tsconfig.json +5 -0
- package/src/tsconfig/mcp/tsconfig.json +6 -0
- package/src/tsconfig/openai/tsconfig.json +7 -0
- package/src/types/application-manifest.ts +7 -1
- package/src/utilities/__tests__/cacheAsync.test.ts +64 -0
- package/src/utilities/cacheAsync.ts +25 -0
- package/src/utilities/constants.ts +5 -0
- package/src/utilities/emptyModule.ts +1 -0
- package/src/utilities/getVariablesForOperationFromToolInput.ts +27 -0
- package/src/utilities/index.ts +5 -0
- package/src/utilities/invariant.ts +5 -0
- package/src/utilities/promiseWithResolvers.ts +18 -0
- package/src/vite/__tests__/apolloClientAiApps.test.ts +1521 -0
- package/src/vite/__tests__/utilities/build.ts +72 -0
- package/src/vite/apolloClientAiApps.ts +515 -0
- package/src/vite/index.ts +1 -2
- package/src/vite/utilities/config.ts +13 -0
- package/src/vite/utilities/graphql.ts +123 -0
- package/tsconfig.base.json +2 -2
- package/tsconfig.vite.build.json +4 -1
- package/tsconfig.vite.json +8 -2
- package/vitest-setup.ts +28 -0
- package/dist/react/context/ToolUseContext.d.ts +0 -16
- package/dist/react/context/ToolUseContext.d.ts.map +0 -1
- package/dist/react/context/ToolUseContext.js +0 -11
- package/dist/react/context/ToolUseContext.js.map +0 -1
- package/dist/react/hooks/useCallTool.d.ts +0 -4
- package/dist/react/hooks/useCallTool.d.ts.map +0 -1
- package/dist/react/hooks/useCallTool.js +0 -5
- package/dist/react/hooks/useCallTool.js.map +0 -1
- package/dist/react/hooks/useOpenAiGlobal.d.ts +0 -3
- package/dist/react/hooks/useOpenAiGlobal.d.ts.map +0 -1
- package/dist/react/hooks/useOpenAiGlobal.js.map +0 -1
- package/dist/react/hooks/useOpenExternal.d.ts +0 -4
- package/dist/react/hooks/useOpenExternal.d.ts.map +0 -1
- package/dist/react/hooks/useOpenExternal.js +0 -5
- package/dist/react/hooks/useOpenExternal.js.map +0 -1
- package/dist/react/hooks/useRequestDisplayMode.d.ts +0 -7
- package/dist/react/hooks/useRequestDisplayMode.d.ts.map +0 -1
- package/dist/react/hooks/useRequestDisplayMode.js +0 -6
- package/dist/react/hooks/useRequestDisplayMode.js.map +0 -1
- package/dist/react/hooks/useSendFollowUpMessage.d.ts +0 -2
- package/dist/react/hooks/useSendFollowUpMessage.d.ts.map +0 -1
- package/dist/react/hooks/useSendFollowUpMessage.js +0 -8
- package/dist/react/hooks/useSendFollowUpMessage.js.map +0 -1
- package/dist/react/hooks/useToolEffect.d.ts +0 -3
- package/dist/react/hooks/useToolEffect.d.ts.map +0 -1
- package/dist/react/hooks/useToolEffect.js +0 -28
- package/dist/react/hooks/useToolEffect.js.map +0 -1
- package/dist/react/hooks/useToolInput.d.ts +0 -2
- package/dist/react/hooks/useToolInput.d.ts.map +0 -1
- package/dist/react/hooks/useToolInput.js +0 -6
- package/dist/react/hooks/useToolInput.js.map +0 -1
- package/dist/react/hooks/useToolName.d.ts.map +0 -1
- package/dist/react/hooks/useToolName.js +0 -6
- package/dist/react/hooks/useToolName.js.map +0 -1
- package/dist/react/hooks/useToolOutput.d.ts +0 -2
- package/dist/react/hooks/useToolOutput.d.ts.map +0 -1
- package/dist/react/hooks/useToolOutput.js +0 -5
- package/dist/react/hooks/useToolOutput.js.map +0 -1
- package/dist/react/hooks/useToolResponseMetadata.d.ts +0 -2
- package/dist/react/hooks/useToolResponseMetadata.d.ts.map +0 -1
- package/dist/react/hooks/useToolResponseMetadata.js +0 -5
- package/dist/react/hooks/useToolResponseMetadata.js.map +0 -1
- package/dist/react/hooks/useWidgetState.d.ts.map +0 -1
- package/dist/react/hooks/useWidgetState.js.map +0 -1
- package/dist/types/openai.d.ts.map +0 -1
- package/dist/types/openai.js.map +0 -1
- package/dist/vite/absolute_asset_imports_plugin.d.ts +0 -5
- package/dist/vite/absolute_asset_imports_plugin.d.ts.map +0 -1
- package/dist/vite/absolute_asset_imports_plugin.js +0 -17
- package/dist/vite/absolute_asset_imports_plugin.js.map +0 -1
- package/dist/vite/application_manifest_plugin.d.ts +0 -10
- package/dist/vite/application_manifest_plugin.d.ts.map +0 -1
- package/dist/vite/application_manifest_plugin.js +0 -317
- package/dist/vite/application_manifest_plugin.js.map +0 -1
- package/src/core/__tests__/ApolloClient.test.ts +0 -646
- package/src/react/__tests__/ApolloProvider.test.tsx +0 -41
- package/src/react/context/ToolUseContext.tsx +0 -31
- package/src/react/hooks/__tests__/useCallTool.test.ts +0 -46
- package/src/react/hooks/__tests__/useOpenExternal.test.tsx +0 -24
- package/src/react/hooks/__tests__/useRequestDisplayMode.test.ts +0 -17
- package/src/react/hooks/__tests__/useSendFollowUpMessage.test.ts +0 -15
- package/src/react/hooks/__tests__/useToolEffect.test.tsx +0 -67
- package/src/react/hooks/__tests__/useToolInput.test.ts +0 -13
- package/src/react/hooks/__tests__/useToolName.test.ts +0 -13
- package/src/react/hooks/__tests__/useToolOutput.test.tsx +0 -49
- package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +0 -49
- package/src/react/hooks/useCallTool.ts +0 -13
- package/src/react/hooks/useOpenExternal.ts +0 -11
- package/src/react/hooks/useRequestDisplayMode.ts +0 -7
- package/src/react/hooks/useSendFollowUpMessage.ts +0 -7
- package/src/react/hooks/useToolEffect.tsx +0 -37
- package/src/react/hooks/useToolInput.ts +0 -7
- package/src/react/hooks/useToolName.ts +0 -7
- package/src/react/hooks/useToolOutput.ts +0 -5
- package/src/react/hooks/useToolResponseMetadata.ts +0 -5
- package/src/vite/__tests__/absolute_asset_imports_plugin.test.ts +0 -102
- package/src/vite/__tests__/application_manifest_plugin.test.ts +0 -1199
- package/src/vite/absolute_asset_imports_plugin.ts +0 -22
- package/src/vite/application_manifest_plugin.ts +0 -539
- /package/dist/{react → openai/react}/hooks/useToolName.d.ts +0 -0
- /package/dist/{react → openai/react}/hooks/useWidgetState.js +0 -0
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { gql, type DocumentNode } from "@apollo/client";
|
|
4
|
+
import { print } from "@apollo/client/utilities";
|
|
5
|
+
import { getOperationName } from "@apollo/client/utilities/internal";
|
|
6
|
+
import { vol } from "memfs";
|
|
7
|
+
import { apolloClientAiApps } from "../apolloClientAiApps.js";
|
|
8
|
+
import { buildApp, setupServer } from "./utilities/build.js";
|
|
9
|
+
import type {
|
|
10
|
+
ApplicationManifest,
|
|
11
|
+
ManifestWidgetSettings,
|
|
12
|
+
} from "../../types/application-manifest.js";
|
|
13
|
+
import { explorer } from "../utilities/config.js";
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
explorer.clearCaches();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("operations", () => {
|
|
20
|
+
test("writes to dev application manifest file when using a serve command", async () => {
|
|
21
|
+
vol.fromJSON({
|
|
22
|
+
"package.json": mockPackageJson({
|
|
23
|
+
"apollo-client-ai-apps": {
|
|
24
|
+
labels: {
|
|
25
|
+
toolInvocation: {
|
|
26
|
+
invoking: "Testing global...",
|
|
27
|
+
invoked: "Tested global!",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
widgetSettings: {
|
|
31
|
+
description: "Test",
|
|
32
|
+
domain: "https://example.com",
|
|
33
|
+
prefersBorder: true,
|
|
34
|
+
} satisfies ManifestWidgetSettings,
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
38
|
+
query HelloWorldQuery($name: string!)
|
|
39
|
+
@tool(
|
|
40
|
+
name: "hello-world"
|
|
41
|
+
description: "This is an awesome tool!"
|
|
42
|
+
extraInputs: [
|
|
43
|
+
{
|
|
44
|
+
name: "doStuff"
|
|
45
|
+
type: "boolean"
|
|
46
|
+
description: "Should we do stuff?"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
labels: {
|
|
50
|
+
toolInvocation: {
|
|
51
|
+
invoking: "Testing tool..."
|
|
52
|
+
invoked: "Tested tool!"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
) {
|
|
56
|
+
helloWorld(name: $name)
|
|
57
|
+
}
|
|
58
|
+
`),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await using server = await setupServer({
|
|
62
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
63
|
+
});
|
|
64
|
+
await server.listen();
|
|
65
|
+
|
|
66
|
+
const manifest = readManifestFile();
|
|
67
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
68
|
+
{
|
|
69
|
+
"appVersion": "1.0.0",
|
|
70
|
+
"csp": {
|
|
71
|
+
"connectDomains": [],
|
|
72
|
+
"frameDomains": [],
|
|
73
|
+
"redirectDomains": [],
|
|
74
|
+
"resourceDomains": [],
|
|
75
|
+
},
|
|
76
|
+
"format": "apollo-ai-app-manifest",
|
|
77
|
+
"hash": "abc",
|
|
78
|
+
"labels": {
|
|
79
|
+
"toolInvocation/invoked": "Tested global!",
|
|
80
|
+
"toolInvocation/invoking": "Testing global...",
|
|
81
|
+
},
|
|
82
|
+
"operations": [
|
|
83
|
+
{
|
|
84
|
+
"body": "query HelloWorldQuery($name: string!) {
|
|
85
|
+
helloWorld(name: $name)
|
|
86
|
+
}",
|
|
87
|
+
"id": "c2ceb00338549909d9a8cd5cc601bda78d8c27654294dfe408a6c3735beb26a6",
|
|
88
|
+
"name": "HelloWorldQuery",
|
|
89
|
+
"prefetch": false,
|
|
90
|
+
"tools": [
|
|
91
|
+
{
|
|
92
|
+
"description": "This is an awesome tool!",
|
|
93
|
+
"extraInputs": [
|
|
94
|
+
{
|
|
95
|
+
"description": "Should we do stuff?",
|
|
96
|
+
"name": "doStuff",
|
|
97
|
+
"type": "boolean",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
"labels": {
|
|
101
|
+
"toolInvocation/invoked": "Tested tool!",
|
|
102
|
+
"toolInvocation/invoking": "Testing tool...",
|
|
103
|
+
},
|
|
104
|
+
"name": "hello-world",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
"type": "query",
|
|
108
|
+
"variables": {
|
|
109
|
+
"name": "string",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
"resource": "http://localhost:3333",
|
|
114
|
+
"version": "1",
|
|
115
|
+
"widgetSettings": {
|
|
116
|
+
"description": "Test",
|
|
117
|
+
"domain": "https://example.com",
|
|
118
|
+
"prefersBorder": true,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("does not write to dev application manifest file when using a build command", async () => {
|
|
125
|
+
vol.fromJSON({
|
|
126
|
+
"package.json": mockPackageJson(),
|
|
127
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
128
|
+
query HelloWorldQuery
|
|
129
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
130
|
+
helloWorld
|
|
131
|
+
}
|
|
132
|
+
`),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await buildApp({
|
|
136
|
+
mode: "production",
|
|
137
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const manifest = readManifestFile();
|
|
141
|
+
expect(manifest.operations).toHaveLength(1);
|
|
142
|
+
expect(manifest.operations[0].name).toBe("HelloWorldQuery");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("does not process files that do not contain gql tags", async () => {
|
|
146
|
+
vol.fromJSON({
|
|
147
|
+
"package.json": mockPackageJson(),
|
|
148
|
+
"src/my-component.tsx": `
|
|
149
|
+
const MY_QUERY = \`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
|
|
150
|
+
`,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await using server = await setupServer({
|
|
154
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
155
|
+
});
|
|
156
|
+
await server.listen();
|
|
157
|
+
|
|
158
|
+
const manifest = readManifestFile();
|
|
159
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
160
|
+
{
|
|
161
|
+
"appVersion": "1.0.0",
|
|
162
|
+
"csp": {
|
|
163
|
+
"connectDomains": [],
|
|
164
|
+
"frameDomains": [],
|
|
165
|
+
"redirectDomains": [],
|
|
166
|
+
"resourceDomains": [],
|
|
167
|
+
},
|
|
168
|
+
"format": "apollo-ai-app-manifest",
|
|
169
|
+
"hash": "abc",
|
|
170
|
+
"operations": [],
|
|
171
|
+
"resource": "http://localhost:3333",
|
|
172
|
+
"version": "1",
|
|
173
|
+
}
|
|
174
|
+
`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("captures queries in manifest file", async () => {
|
|
178
|
+
vol.fromJSON({
|
|
179
|
+
"package.json": mockPackageJson(),
|
|
180
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
181
|
+
query HelloWorldQuery {
|
|
182
|
+
helloWorld
|
|
183
|
+
}
|
|
184
|
+
`),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await using server = await setupServer({
|
|
188
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
189
|
+
});
|
|
190
|
+
await server.listen();
|
|
191
|
+
|
|
192
|
+
const manifest = readManifestFile();
|
|
193
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
194
|
+
{
|
|
195
|
+
"appVersion": "1.0.0",
|
|
196
|
+
"csp": {
|
|
197
|
+
"connectDomains": [],
|
|
198
|
+
"frameDomains": [],
|
|
199
|
+
"redirectDomains": [],
|
|
200
|
+
"resourceDomains": [],
|
|
201
|
+
},
|
|
202
|
+
"format": "apollo-ai-app-manifest",
|
|
203
|
+
"hash": "abc",
|
|
204
|
+
"operations": [
|
|
205
|
+
{
|
|
206
|
+
"body": "query HelloWorldQuery {
|
|
207
|
+
helloWorld
|
|
208
|
+
}",
|
|
209
|
+
"id": "f8604bba13e2f589608c0eb36c3039c5ef3a4c5747bc1596f9dbcbe924dc90f9",
|
|
210
|
+
"name": "HelloWorldQuery",
|
|
211
|
+
"prefetch": false,
|
|
212
|
+
"tools": [],
|
|
213
|
+
"type": "query",
|
|
214
|
+
"variables": {},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
"resource": "http://localhost:3333",
|
|
218
|
+
"version": "1",
|
|
219
|
+
}
|
|
220
|
+
`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("captures mutations in manifest file", async () => {
|
|
224
|
+
vol.fromJSON({
|
|
225
|
+
"package.json": mockPackageJson(),
|
|
226
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
227
|
+
mutation HelloWorldQuery
|
|
228
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
229
|
+
helloWorld
|
|
230
|
+
}
|
|
231
|
+
`),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await using server = await setupServer({
|
|
235
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
236
|
+
});
|
|
237
|
+
await server.listen();
|
|
238
|
+
|
|
239
|
+
const manifest = readManifestFile();
|
|
240
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
241
|
+
{
|
|
242
|
+
"appVersion": "1.0.0",
|
|
243
|
+
"csp": {
|
|
244
|
+
"connectDomains": [],
|
|
245
|
+
"frameDomains": [],
|
|
246
|
+
"redirectDomains": [],
|
|
247
|
+
"resourceDomains": [],
|
|
248
|
+
},
|
|
249
|
+
"format": "apollo-ai-app-manifest",
|
|
250
|
+
"hash": "abc",
|
|
251
|
+
"operations": [
|
|
252
|
+
{
|
|
253
|
+
"body": "mutation HelloWorldQuery {
|
|
254
|
+
helloWorld
|
|
255
|
+
}",
|
|
256
|
+
"id": "0c98e15f08542215c9c268192aaff732800bc33b79dddea7dc6fdf69c21b61a7",
|
|
257
|
+
"name": "HelloWorldQuery",
|
|
258
|
+
"prefetch": false,
|
|
259
|
+
"tools": [
|
|
260
|
+
{
|
|
261
|
+
"description": "This is an awesome tool!",
|
|
262
|
+
"name": "hello-world",
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
"type": "mutation",
|
|
266
|
+
"variables": {},
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
"resource": "http://localhost:3333",
|
|
270
|
+
"version": "1",
|
|
271
|
+
}
|
|
272
|
+
`);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("errors when a subscription operation type is discovered", async () => {
|
|
276
|
+
vol.fromJSON({
|
|
277
|
+
"package.json": mockPackageJson(),
|
|
278
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
279
|
+
subscription HelloWorldQuery
|
|
280
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
281
|
+
helloWorld
|
|
282
|
+
}
|
|
283
|
+
`),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await expect(async () => {
|
|
287
|
+
await using server = await setupServer({
|
|
288
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
289
|
+
});
|
|
290
|
+
await server.listen();
|
|
291
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
292
|
+
`[Error: Found an unsupported operation type. Only Query and Mutation are supported.]`
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("orders operations and fragments when generating normalized operation", async () => {
|
|
297
|
+
vol.fromJSON({
|
|
298
|
+
"package.json": mockPackageJson(),
|
|
299
|
+
"src/my-component.tsx": `
|
|
300
|
+
const MY_QUERY = gql\`
|
|
301
|
+
fragment A on User { firstName }
|
|
302
|
+
fragment B on User { lastName }
|
|
303
|
+
query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
304
|
+
helloWorld {
|
|
305
|
+
...B
|
|
306
|
+
...A
|
|
307
|
+
...C
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fragment C on User { middleName }
|
|
311
|
+
}\`;
|
|
312
|
+
`,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await using server = await setupServer({
|
|
316
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
317
|
+
});
|
|
318
|
+
await server.listen();
|
|
319
|
+
|
|
320
|
+
const manifest = readManifestFile();
|
|
321
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
322
|
+
{
|
|
323
|
+
"appVersion": "1.0.0",
|
|
324
|
+
"csp": {
|
|
325
|
+
"connectDomains": [],
|
|
326
|
+
"frameDomains": [],
|
|
327
|
+
"redirectDomains": [],
|
|
328
|
+
"resourceDomains": [],
|
|
329
|
+
},
|
|
330
|
+
"format": "apollo-ai-app-manifest",
|
|
331
|
+
"hash": "abc",
|
|
332
|
+
"operations": [
|
|
333
|
+
{
|
|
334
|
+
"body": "query HelloWorldQuery {
|
|
335
|
+
helloWorld {
|
|
336
|
+
...B
|
|
337
|
+
...A
|
|
338
|
+
...C
|
|
339
|
+
__typename
|
|
340
|
+
}
|
|
341
|
+
fragment
|
|
342
|
+
C
|
|
343
|
+
on
|
|
344
|
+
User {
|
|
345
|
+
middleName
|
|
346
|
+
__typename
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
fragment A on User {
|
|
351
|
+
firstName
|
|
352
|
+
__typename
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fragment B on User {
|
|
356
|
+
lastName
|
|
357
|
+
__typename
|
|
358
|
+
}",
|
|
359
|
+
"id": "58359ad006a8e1a6cdabe4b49c0322e8a41d71c5194a796e6432be055220b9ec",
|
|
360
|
+
"name": "HelloWorldQuery",
|
|
361
|
+
"prefetch": false,
|
|
362
|
+
"tools": [
|
|
363
|
+
{
|
|
364
|
+
"description": "This is an awesome tool!",
|
|
365
|
+
"name": "hello-world",
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
"type": "query",
|
|
369
|
+
"variables": {},
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
"resource": "http://localhost:3333",
|
|
373
|
+
"version": "1",
|
|
374
|
+
}
|
|
375
|
+
`);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("@prefetch", () => {
|
|
380
|
+
test("captures queries as prefetch when marked with @prefetch directive", async () => {
|
|
381
|
+
vol.fromJSON({
|
|
382
|
+
"package.json": mockPackageJson(),
|
|
383
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
384
|
+
query HelloWorldQuery @prefetch {
|
|
385
|
+
helloWorld
|
|
386
|
+
}
|
|
387
|
+
`),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
await using server = await setupServer({
|
|
391
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
392
|
+
});
|
|
393
|
+
await server.listen();
|
|
394
|
+
|
|
395
|
+
const manifest = readManifestFile();
|
|
396
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
397
|
+
{
|
|
398
|
+
"appVersion": "1.0.0",
|
|
399
|
+
"csp": {
|
|
400
|
+
"connectDomains": [],
|
|
401
|
+
"frameDomains": [],
|
|
402
|
+
"redirectDomains": [],
|
|
403
|
+
"resourceDomains": [],
|
|
404
|
+
},
|
|
405
|
+
"format": "apollo-ai-app-manifest",
|
|
406
|
+
"hash": "abc",
|
|
407
|
+
"operations": [
|
|
408
|
+
{
|
|
409
|
+
"body": "query HelloWorldQuery {
|
|
410
|
+
helloWorld
|
|
411
|
+
}",
|
|
412
|
+
"id": "f8604bba13e2f589608c0eb36c3039c5ef3a4c5747bc1596f9dbcbe924dc90f9",
|
|
413
|
+
"name": "HelloWorldQuery",
|
|
414
|
+
"prefetch": true,
|
|
415
|
+
"prefetchID": "__anonymous",
|
|
416
|
+
"tools": [],
|
|
417
|
+
"type": "query",
|
|
418
|
+
"variables": {},
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
"resource": "http://localhost:3333",
|
|
422
|
+
"version": "1",
|
|
423
|
+
}
|
|
424
|
+
`);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("errors when multiple operations are marked with @prefetch", async () => {
|
|
428
|
+
vol.fromJSON({
|
|
429
|
+
"package.json": mockPackageJson(),
|
|
430
|
+
"src/my-component.tsx": [
|
|
431
|
+
declareOperation(gql`
|
|
432
|
+
query HelloWorldQuery @prefetch {
|
|
433
|
+
helloWorld
|
|
434
|
+
}
|
|
435
|
+
`),
|
|
436
|
+
declareOperation(gql`
|
|
437
|
+
query HelloWorldQuery2 @prefetch {
|
|
438
|
+
helloWorld
|
|
439
|
+
}
|
|
440
|
+
`),
|
|
441
|
+
].join("\n"),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
await expect(async () => {
|
|
445
|
+
await using server = await setupServer({
|
|
446
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
447
|
+
});
|
|
448
|
+
await server.listen();
|
|
449
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
450
|
+
`[Error: Found multiple operations marked as \`@prefetch\`. You can only mark 1 operation with \`@prefetch\`.]`
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
describe("@tool validation", () => {
|
|
456
|
+
test("errors when tool name is not provided", async () => {
|
|
457
|
+
vol.fromJSON({
|
|
458
|
+
"package.json": mockPackageJson(),
|
|
459
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
460
|
+
query HelloWorldQuery @tool {
|
|
461
|
+
helloWorld
|
|
462
|
+
}
|
|
463
|
+
`),
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
await expect(async () => {
|
|
467
|
+
await using server = await setupServer({
|
|
468
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
469
|
+
});
|
|
470
|
+
await server.listen();
|
|
471
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
472
|
+
`[Error: 'name' argument must be supplied for @tool]`
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("errors when tool description is not provided", async () => {
|
|
477
|
+
vol.fromJSON({
|
|
478
|
+
"package.json": mockPackageJson(),
|
|
479
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
480
|
+
query HelloWorldQuery @tool(name: "hello-world") {
|
|
481
|
+
helloWorld
|
|
482
|
+
}
|
|
483
|
+
`),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await expect(async () => {
|
|
487
|
+
await using server = await setupServer({
|
|
488
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
489
|
+
});
|
|
490
|
+
await server.listen();
|
|
491
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
492
|
+
`[Error: 'description' argument must be supplied for @tool]`
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("errors when tool name contains spaces", async () => {
|
|
497
|
+
vol.fromJSON({
|
|
498
|
+
"package.json": mockPackageJson(),
|
|
499
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
500
|
+
query HelloWorldQuery
|
|
501
|
+
@tool(name: "hello world", description: "A tool") {
|
|
502
|
+
helloWorld
|
|
503
|
+
}
|
|
504
|
+
`),
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
await expect(async () => {
|
|
508
|
+
await using server = await setupServer({
|
|
509
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
510
|
+
});
|
|
511
|
+
await server.listen();
|
|
512
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
513
|
+
`
|
|
514
|
+
[Error: ✖ Tool with name "hello world" must not contain spaces
|
|
515
|
+
→ at name]
|
|
516
|
+
`
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("errors when tool name is not a string", async () => {
|
|
521
|
+
vol.fromJSON({
|
|
522
|
+
"package.json": mockPackageJson(),
|
|
523
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
524
|
+
query HelloWorldQuery @tool(name: true) {
|
|
525
|
+
helloWorld
|
|
526
|
+
}
|
|
527
|
+
`),
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await expect(async () => {
|
|
531
|
+
await using server = await setupServer({
|
|
532
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
533
|
+
});
|
|
534
|
+
await server.listen();
|
|
535
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
536
|
+
`[Error: Expected argument 'name' to be of type 'StringValue' but found 'BooleanValue' instead.]`
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("errors when tool description is not a string", async () => {
|
|
541
|
+
vol.fromJSON({
|
|
542
|
+
"package.json": mockPackageJson(),
|
|
543
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
544
|
+
query HelloWorldQuery @tool(name: "hello-world", description: false) {
|
|
545
|
+
helloWorld
|
|
546
|
+
}
|
|
547
|
+
`),
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
await expect(async () => {
|
|
551
|
+
await using server = await setupServer({
|
|
552
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
553
|
+
});
|
|
554
|
+
await server.listen();
|
|
555
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
556
|
+
`[Error: Expected argument 'description' to be of type 'StringValue' but found 'BooleanValue' instead.]`
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("errors when extraInputs is not an array", async () => {
|
|
561
|
+
vol.fromJSON({
|
|
562
|
+
"package.json": mockPackageJson(),
|
|
563
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
564
|
+
query HelloWorldQuery
|
|
565
|
+
@tool(name: "hello-world", description: "hello", extraInputs: false) {
|
|
566
|
+
helloWorld
|
|
567
|
+
}
|
|
568
|
+
`),
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
await expect(async () => {
|
|
572
|
+
await using server = await setupServer({
|
|
573
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
574
|
+
});
|
|
575
|
+
await server.listen();
|
|
576
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
577
|
+
`[Error: Expected argument 'extraInputs' to be of type 'ListValue' but found 'BooleanValue' instead.]`
|
|
578
|
+
);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test("errors when an unknown type is discovered", async () => {
|
|
582
|
+
vol.fromJSON({
|
|
583
|
+
"package.json": mockPackageJson(),
|
|
584
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
585
|
+
query HelloWorldQuery
|
|
586
|
+
@tool(
|
|
587
|
+
name: "hello-world"
|
|
588
|
+
description: "hello"
|
|
589
|
+
extraInputs: [{ name: 3.1 }]
|
|
590
|
+
) {
|
|
591
|
+
helloWorld
|
|
592
|
+
}
|
|
593
|
+
`),
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await expect(async () => {
|
|
597
|
+
await using server = await setupServer({
|
|
598
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
599
|
+
});
|
|
600
|
+
await server.listen();
|
|
601
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
602
|
+
`[Error: Error when parsing directive values: unexpected type 'FloatValue']`
|
|
603
|
+
);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe("config validation", () => {
|
|
608
|
+
test("errors when widgetSettings.prefersBorder is not a boolean", async () => {
|
|
609
|
+
vol.fromJSON({
|
|
610
|
+
"package.json": mockPackageJson({
|
|
611
|
+
"apollo-client-ai-apps": {
|
|
612
|
+
widgetSettings: {
|
|
613
|
+
prefersBorder: "test",
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
}),
|
|
617
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
618
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
619
|
+
helloWorld
|
|
620
|
+
}
|
|
621
|
+
`),
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
await expect(async () => {
|
|
625
|
+
await using server = await setupServer({
|
|
626
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
627
|
+
});
|
|
628
|
+
await server.listen();
|
|
629
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
630
|
+
`
|
|
631
|
+
"✖ Invalid input: expected boolean, received string
|
|
632
|
+
→ at widgetSettings.prefersBorder"
|
|
633
|
+
`
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("errors when widgetSettings.description is not a string", async () => {
|
|
638
|
+
vol.fromJSON({
|
|
639
|
+
"package.json": mockPackageJson({
|
|
640
|
+
"apollo-client-ai-apps": {
|
|
641
|
+
widgetSettings: {
|
|
642
|
+
description: true,
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
}),
|
|
646
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
647
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
648
|
+
helloWorld
|
|
649
|
+
}
|
|
650
|
+
`),
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
await expect(async () => {
|
|
654
|
+
await using server = await setupServer({
|
|
655
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
656
|
+
});
|
|
657
|
+
await server.listen();
|
|
658
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
659
|
+
`
|
|
660
|
+
"✖ Invalid input: expected string, received boolean
|
|
661
|
+
→ at widgetSettings.description"
|
|
662
|
+
`
|
|
663
|
+
);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("errors when widgetSettings.domain is not a string", async () => {
|
|
667
|
+
vol.fromJSON({
|
|
668
|
+
"package.json": mockPackageJson({
|
|
669
|
+
"apollo-client-ai-apps": {
|
|
670
|
+
widgetSettings: {
|
|
671
|
+
domain: true,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
}),
|
|
675
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
676
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
677
|
+
helloWorld
|
|
678
|
+
}
|
|
679
|
+
`),
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
await expect(async () => {
|
|
683
|
+
await using server = await setupServer({
|
|
684
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
685
|
+
});
|
|
686
|
+
await server.listen();
|
|
687
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
688
|
+
`
|
|
689
|
+
"✖ Invalid input: expected string, received boolean
|
|
690
|
+
→ at widgetSettings.domain"
|
|
691
|
+
`
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("allows empty widgetSettings value", async () => {
|
|
696
|
+
vol.fromJSON({
|
|
697
|
+
"package.json": mockPackageJson({
|
|
698
|
+
widgetSettings: {},
|
|
699
|
+
}),
|
|
700
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
701
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
702
|
+
helloWorld
|
|
703
|
+
}
|
|
704
|
+
`),
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
await using server = await setupServer({
|
|
708
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
709
|
+
});
|
|
710
|
+
await server.listen();
|
|
711
|
+
|
|
712
|
+
const manifest = readManifestFile();
|
|
713
|
+
expect(manifest.operations).toHaveLength(1);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
test("errors when labels.toolInvocation.invoking in package.json is not a string", async () => {
|
|
717
|
+
vol.fromJSON({
|
|
718
|
+
"package.json": mockPackageJson({
|
|
719
|
+
"apollo-client-ai-apps": {
|
|
720
|
+
labels: {
|
|
721
|
+
toolInvocation: {
|
|
722
|
+
invoking: true,
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
}),
|
|
727
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
728
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
729
|
+
helloWorld
|
|
730
|
+
}
|
|
731
|
+
`),
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
await expect(async () => {
|
|
735
|
+
await using server = await setupServer({
|
|
736
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
737
|
+
});
|
|
738
|
+
await server.listen();
|
|
739
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
740
|
+
`
|
|
741
|
+
"✖ Invalid input: expected string, received boolean
|
|
742
|
+
→ at labels.toolInvocation.invoking"
|
|
743
|
+
`
|
|
744
|
+
);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test("errors when labels.toolInvocation.invoking in @tool is not a string", async () => {
|
|
748
|
+
vol.fromJSON({
|
|
749
|
+
"package.json": mockPackageJson(),
|
|
750
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
751
|
+
query HelloWorldQuery
|
|
752
|
+
@tool(
|
|
753
|
+
name: "test"
|
|
754
|
+
description: "Test"
|
|
755
|
+
labels: { toolInvocation: { invoking: true } }
|
|
756
|
+
) {
|
|
757
|
+
helloWorld
|
|
758
|
+
}
|
|
759
|
+
`),
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
await expect(async () => {
|
|
763
|
+
await using server = await setupServer({
|
|
764
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
765
|
+
});
|
|
766
|
+
await server.listen();
|
|
767
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
768
|
+
`
|
|
769
|
+
[Error: ✖ Invalid input: expected string, received boolean
|
|
770
|
+
→ at labels.toolInvocation.invoking]
|
|
771
|
+
`
|
|
772
|
+
);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test("errors when labels.toolInvocation.invoked in package.json is not a string", async () => {
|
|
776
|
+
vol.fromJSON({
|
|
777
|
+
"package.json": mockPackageJson({
|
|
778
|
+
"apollo-client-ai-apps": {
|
|
779
|
+
labels: {
|
|
780
|
+
toolInvocation: {
|
|
781
|
+
invoked: true,
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
}),
|
|
786
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
787
|
+
query HelloWorldQuery @tool(name: "test", description: "Test") {
|
|
788
|
+
helloWorld
|
|
789
|
+
}
|
|
790
|
+
`),
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
await expect(async () => {
|
|
794
|
+
await using server = await setupServer({
|
|
795
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
796
|
+
});
|
|
797
|
+
await server.listen();
|
|
798
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
799
|
+
`
|
|
800
|
+
"✖ Invalid input: expected string, received boolean
|
|
801
|
+
→ at labels.toolInvocation.invoked"
|
|
802
|
+
`
|
|
803
|
+
);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test("errors when labels.toolInvocation.invoked in @tool is not a string", async () => {
|
|
807
|
+
vol.fromJSON({
|
|
808
|
+
"package.json": mockPackageJson(),
|
|
809
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
810
|
+
query HelloWorldQuery
|
|
811
|
+
@tool(
|
|
812
|
+
name: "test"
|
|
813
|
+
description: "Test"
|
|
814
|
+
labels: { toolInvocation: { invoked: true } }
|
|
815
|
+
) {
|
|
816
|
+
helloWorld
|
|
817
|
+
}
|
|
818
|
+
`),
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
await expect(async () => {
|
|
822
|
+
await using server = await setupServer({
|
|
823
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
824
|
+
});
|
|
825
|
+
await server.listen();
|
|
826
|
+
}).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
827
|
+
`
|
|
828
|
+
[Error: ✖ Invalid input: expected string, received boolean
|
|
829
|
+
→ at labels.toolInvocation.invoked]
|
|
830
|
+
`
|
|
831
|
+
);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
test("allows empty labels value", async () => {
|
|
835
|
+
vol.fromJSON({
|
|
836
|
+
"package.json": mockPackageJson({ labels: {} }),
|
|
837
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
838
|
+
query HelloWorldQuery
|
|
839
|
+
@tool(name: "test", description: "Test", labels: {}) {
|
|
840
|
+
helloWorld
|
|
841
|
+
}
|
|
842
|
+
`),
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
await using server = await setupServer({
|
|
846
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
847
|
+
});
|
|
848
|
+
await server.listen();
|
|
849
|
+
|
|
850
|
+
const manifest = readManifestFile();
|
|
851
|
+
expect(manifest.operations).toHaveLength(1);
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
describe("entry points", () => {
|
|
856
|
+
test("uses custom entry point when in serve mode and provided in package.json", async () => {
|
|
857
|
+
vol.fromJSON({
|
|
858
|
+
"package.json": mockPackageJson({
|
|
859
|
+
"apollo-client-ai-apps": {
|
|
860
|
+
entry: {
|
|
861
|
+
staging: "http://staging.awesome.com",
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
}),
|
|
865
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
866
|
+
query HelloWorldQuery
|
|
867
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
868
|
+
helloWorld
|
|
869
|
+
}
|
|
870
|
+
`),
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
await using server = await setupServer({
|
|
874
|
+
mode: "staging",
|
|
875
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
876
|
+
});
|
|
877
|
+
await server.listen();
|
|
878
|
+
|
|
879
|
+
const manifest = readManifestFile();
|
|
880
|
+
expect(manifest.resource).toBe("http://staging.awesome.com");
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("uses custom entry point for devTarget when in serve mode and provided in package.json", async () => {
|
|
884
|
+
vol.fromJSON({
|
|
885
|
+
"package.json": mockPackageJson({
|
|
886
|
+
"apollo-client-ai-apps": {
|
|
887
|
+
entry: {
|
|
888
|
+
staging: {
|
|
889
|
+
mcp: "http://staging.awesome.com",
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
}),
|
|
894
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
895
|
+
query HelloWorldQuery
|
|
896
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
897
|
+
helloWorld
|
|
898
|
+
}
|
|
899
|
+
`),
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
await using server = await setupServer({
|
|
903
|
+
mode: "staging",
|
|
904
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"], devTarget: "mcp" })],
|
|
905
|
+
});
|
|
906
|
+
await server.listen();
|
|
907
|
+
|
|
908
|
+
const manifest = readManifestFile();
|
|
909
|
+
expect(manifest.resource).toBe("http://staging.awesome.com");
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test("uses https when enabled in server config", async () => {
|
|
913
|
+
vol.fromJSON({
|
|
914
|
+
"package.json": mockPackageJson(),
|
|
915
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
916
|
+
query HelloWorldQuery
|
|
917
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
918
|
+
helloWorld
|
|
919
|
+
}
|
|
920
|
+
`),
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
await using server = await setupServer({
|
|
924
|
+
server: { https: {}, port: 5678 },
|
|
925
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
926
|
+
});
|
|
927
|
+
await server.listen();
|
|
928
|
+
|
|
929
|
+
const manifest = readManifestFile();
|
|
930
|
+
expect(manifest.resource).toBe("https://localhost:5678");
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
test("uses custom host when specified in server config", async () => {
|
|
934
|
+
vol.fromJSON({
|
|
935
|
+
"package.json": mockPackageJson(),
|
|
936
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
937
|
+
query HelloWorldQuery
|
|
938
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
939
|
+
helloWorld
|
|
940
|
+
}
|
|
941
|
+
`),
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
await using server = await setupServer({
|
|
945
|
+
server: { port: 5678, host: "0.0.0.0" },
|
|
946
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
947
|
+
});
|
|
948
|
+
await server.listen();
|
|
949
|
+
|
|
950
|
+
const manifest = readManifestFile();
|
|
951
|
+
expect(manifest.resource).toBe("http://0.0.0.0:5678");
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
test("uses custom entry point when in build mode and provided in package.json", async () => {
|
|
955
|
+
vol.fromJSON({
|
|
956
|
+
"package.json": mockPackageJson({
|
|
957
|
+
"apollo-client-ai-apps": {
|
|
958
|
+
entry: {
|
|
959
|
+
staging: "http://staging.awesome.com",
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
}),
|
|
963
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
964
|
+
query HelloWorldQuery
|
|
965
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
966
|
+
helloWorld
|
|
967
|
+
}
|
|
968
|
+
`),
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
await buildApp({
|
|
972
|
+
mode: "staging",
|
|
973
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const manifest = readManifestFile();
|
|
977
|
+
expect(manifest.resource).toEqual({ mcp: "http://staging.awesome.com" });
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test("uses custom entry point for target when in build mode with multiple targets", async () => {
|
|
981
|
+
vol.fromJSON({
|
|
982
|
+
"package.json": mockPackageJson({
|
|
983
|
+
"apollo-client-ai-apps": {
|
|
984
|
+
entry: {
|
|
985
|
+
staging: {
|
|
986
|
+
mcp: "http://staging-mcp.awesome.com",
|
|
987
|
+
openai: "http://staging-openai.awesome.com",
|
|
988
|
+
},
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
}),
|
|
992
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
993
|
+
query HelloWorldQuery
|
|
994
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
995
|
+
helloWorld
|
|
996
|
+
}
|
|
997
|
+
`),
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
await buildApp({
|
|
1001
|
+
mode: "staging",
|
|
1002
|
+
plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const manifest = readManifestFile();
|
|
1006
|
+
expect(manifest.resource).toEqual({
|
|
1007
|
+
mcp: "http://staging-mcp.awesome.com",
|
|
1008
|
+
openai: "http://staging-openai.awesome.com",
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
test("uses custom entry point for all targets when in build mode with multiple targets", async () => {
|
|
1013
|
+
vol.fromJSON({
|
|
1014
|
+
"package.json": mockPackageJson({
|
|
1015
|
+
"apollo-client-ai-apps": {
|
|
1016
|
+
entry: {
|
|
1017
|
+
staging: "http://staging.awesome.com",
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
}),
|
|
1021
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1022
|
+
query HelloWorldQuery
|
|
1023
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1024
|
+
helloWorld
|
|
1025
|
+
}
|
|
1026
|
+
`),
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
await buildApp({
|
|
1030
|
+
mode: "staging",
|
|
1031
|
+
plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
const manifest = readManifestFile();
|
|
1035
|
+
expect(manifest.resource).toEqual({
|
|
1036
|
+
mcp: "http://staging.awesome.com",
|
|
1037
|
+
openai: "http://staging.awesome.com",
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
test("uses custom entry point for target when in build mode with single target", async () => {
|
|
1042
|
+
vol.fromJSON({
|
|
1043
|
+
"package.json": mockPackageJson({
|
|
1044
|
+
"apollo-client-ai-apps": {
|
|
1045
|
+
entry: {
|
|
1046
|
+
staging: {
|
|
1047
|
+
mcp: "http://staging-mcp.awesome.com",
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
},
|
|
1051
|
+
}),
|
|
1052
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1053
|
+
query HelloWorldQuery
|
|
1054
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1055
|
+
helloWorld
|
|
1056
|
+
}
|
|
1057
|
+
`),
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
await buildApp({
|
|
1061
|
+
mode: "staging",
|
|
1062
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
const manifest = readManifestFile();
|
|
1066
|
+
expect(manifest.resource).toEqual({
|
|
1067
|
+
mcp: "http://staging-mcp.awesome.com",
|
|
1068
|
+
});
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
test("uses [target]/index.html when in build production and not provided in package.json with single target", async () => {
|
|
1072
|
+
vol.fromJSON({
|
|
1073
|
+
"package.json": mockPackageJson(),
|
|
1074
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1075
|
+
query HelloWorldQuery
|
|
1076
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1077
|
+
helloWorld
|
|
1078
|
+
}
|
|
1079
|
+
`),
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
await buildApp({
|
|
1083
|
+
mode: "production",
|
|
1084
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
const manifest = readManifestFile();
|
|
1088
|
+
expect(manifest.resource).toEqual({ mcp: "mcp/index.html" });
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
test("uses [target]/index.html when in build production with multiple targets", async () => {
|
|
1092
|
+
vol.fromJSON({
|
|
1093
|
+
"package.json": mockPackageJson(),
|
|
1094
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1095
|
+
query HelloWorldQuery
|
|
1096
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1097
|
+
helloWorld
|
|
1098
|
+
}
|
|
1099
|
+
`),
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
await buildApp({
|
|
1103
|
+
mode: "production",
|
|
1104
|
+
plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
const manifest = readManifestFile();
|
|
1108
|
+
expect(manifest.resource).toEqual({
|
|
1109
|
+
mcp: "mcp/index.html",
|
|
1110
|
+
openai: "openai/index.html",
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test("errors when in build mode and using a mode that is not production and not provided in package.json", async () => {
|
|
1115
|
+
vol.fromJSON({
|
|
1116
|
+
"package.json": mockPackageJson(),
|
|
1117
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1118
|
+
query HelloWorldQuery
|
|
1119
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1120
|
+
helloWorld
|
|
1121
|
+
}
|
|
1122
|
+
`),
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
await expect(
|
|
1126
|
+
async () =>
|
|
1127
|
+
await buildApp({
|
|
1128
|
+
mode: "staging",
|
|
1129
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1130
|
+
})
|
|
1131
|
+
).rejects.toThrowError(
|
|
1132
|
+
`[@apollo/client-ai-apps/vite] No entry point found for mode "staging". Entry points other than "development" and "production" must be defined in package.json file.`
|
|
1133
|
+
);
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
test("writes to both locations when running in build mode", async () => {
|
|
1137
|
+
vol.fromJSON({
|
|
1138
|
+
"package.json": mockPackageJson(),
|
|
1139
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1140
|
+
query HelloWorldQuery
|
|
1141
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1142
|
+
helloWorld
|
|
1143
|
+
}
|
|
1144
|
+
`),
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
await buildApp({
|
|
1148
|
+
mode: "production",
|
|
1149
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
expect(vol.existsSync(".application-manifest.json")).toBe(true);
|
|
1153
|
+
expect(vol.existsSync("dist/.application-manifest.json")).toBe(true);
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
test("writes to both locations when running in build mode with multiple targets", async () => {
|
|
1157
|
+
vol.fromJSON({
|
|
1158
|
+
"package.json": mockPackageJson(),
|
|
1159
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1160
|
+
query HelloWorldQuery
|
|
1161
|
+
@tool(name: "hello-world", description: "This is an awesome tool!") {
|
|
1162
|
+
helloWorld
|
|
1163
|
+
}
|
|
1164
|
+
`),
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
await buildApp({
|
|
1168
|
+
mode: "production",
|
|
1169
|
+
plugins: [apolloClientAiApps({ targets: ["mcp", "openai"] })],
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
expect(vol.existsSync(".application-manifest.json")).toBe(true);
|
|
1173
|
+
expect(vol.existsSync("dist/.application-manifest.json")).toBe(true);
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
describe("config files", () => {
|
|
1178
|
+
const appConfigName = "test-app";
|
|
1179
|
+
const appConfigDescription = "test description";
|
|
1180
|
+
|
|
1181
|
+
const json = JSON.stringify({
|
|
1182
|
+
name: appConfigName,
|
|
1183
|
+
description: appConfigDescription,
|
|
1184
|
+
});
|
|
1185
|
+
const yaml = `
|
|
1186
|
+
name: "${appConfigName}"
|
|
1187
|
+
description: "${appConfigDescription}"
|
|
1188
|
+
`;
|
|
1189
|
+
const cjs = `
|
|
1190
|
+
module.exports = {
|
|
1191
|
+
name: "${appConfigName}",
|
|
1192
|
+
description: "${appConfigDescription}",
|
|
1193
|
+
}
|
|
1194
|
+
`;
|
|
1195
|
+
const mjs = `
|
|
1196
|
+
export default {
|
|
1197
|
+
name: "${appConfigName}",
|
|
1198
|
+
description: "${appConfigDescription}"
|
|
1199
|
+
}
|
|
1200
|
+
`;
|
|
1201
|
+
const ts = `
|
|
1202
|
+
import type { ApolloAiAppsConfig } from "@apollo/client-ai-apps/config";
|
|
1203
|
+
|
|
1204
|
+
const config: ApolloAiAppsConfig.Config = {
|
|
1205
|
+
name: "${appConfigName}",
|
|
1206
|
+
description: "${appConfigDescription}",
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
export default config;
|
|
1210
|
+
`;
|
|
1211
|
+
|
|
1212
|
+
test("reads config from package.json", async () => {
|
|
1213
|
+
vol.fromJSON({
|
|
1214
|
+
"package.json": mockPackageJson({
|
|
1215
|
+
"apollo-client-ai-apps": JSON.parse(json),
|
|
1216
|
+
}),
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
await buildApp({
|
|
1220
|
+
mode: "production",
|
|
1221
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
const manifest = readManifestFile();
|
|
1225
|
+
expect(manifest).toMatchObject({
|
|
1226
|
+
name: appConfigName,
|
|
1227
|
+
description: appConfigDescription,
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
test.each([
|
|
1232
|
+
[".apollo-client-ai-apps.config.json", json],
|
|
1233
|
+
["apollo-client-ai-apps.config.json", json],
|
|
1234
|
+
[".apollo-client-ai-apps.config.yml", yaml],
|
|
1235
|
+
["apollo-client-ai-apps.config.yml", yaml],
|
|
1236
|
+
[".apollo-client-ai-apps.config.yaml", yaml],
|
|
1237
|
+
["apollo-client-ai-apps.config.yaml", yaml],
|
|
1238
|
+
[".apollo-client-ai-apps.config.js", cjs],
|
|
1239
|
+
["apollo-client-ai-apps.config.js", cjs],
|
|
1240
|
+
[".apollo-client-ai-apps.config.ts", ts],
|
|
1241
|
+
["apollo-client-ai-apps.config.ts", ts],
|
|
1242
|
+
[".apollo-client-ai-apps.config.cjs", cjs],
|
|
1243
|
+
["apollo-client-ai-apps.config.cjs", cjs],
|
|
1244
|
+
[".apollo-client-ai-apps.config.mjs", mjs],
|
|
1245
|
+
["apollo-client-ai-apps.config.mjs", mjs],
|
|
1246
|
+
])("reads config from %s", async (filepath, contents) => {
|
|
1247
|
+
using _ = interceptWriteESMtoCJS();
|
|
1248
|
+
using __ = await tmpWriteRealFile(filepath, contents);
|
|
1249
|
+
|
|
1250
|
+
vol.fromJSON({
|
|
1251
|
+
"package.json": mockPackageJson(),
|
|
1252
|
+
[filepath]: contents.trimStart(),
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
await buildApp({
|
|
1256
|
+
mode: "production",
|
|
1257
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
const manifest = readManifestFile();
|
|
1261
|
+
expect(manifest).toMatchObject({
|
|
1262
|
+
name: appConfigName,
|
|
1263
|
+
description: appConfigDescription,
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
describe("file watching", () => {
|
|
1269
|
+
test("updates manifest file when a source file is changed", async () => {
|
|
1270
|
+
vol.fromJSON({
|
|
1271
|
+
"package.json": mockPackageJson(),
|
|
1272
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
1273
|
+
query HelloWorldQuery($name: string!)
|
|
1274
|
+
@tool(
|
|
1275
|
+
name: "hello-world"
|
|
1276
|
+
description: "This is an awesome tool!"
|
|
1277
|
+
extraInputs: [
|
|
1278
|
+
{
|
|
1279
|
+
name: "doStuff"
|
|
1280
|
+
type: "boolean"
|
|
1281
|
+
description: "Should we do stuff?"
|
|
1282
|
+
}
|
|
1283
|
+
]
|
|
1284
|
+
) {
|
|
1285
|
+
helloWorld(name: $name)
|
|
1286
|
+
}
|
|
1287
|
+
`),
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
await using server = await setupServer({
|
|
1291
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1292
|
+
});
|
|
1293
|
+
await server.listen();
|
|
1294
|
+
|
|
1295
|
+
const manifestBefore = readManifestFile();
|
|
1296
|
+
expect(manifestBefore.operations).toHaveLength(1);
|
|
1297
|
+
|
|
1298
|
+
vol.writeFileSync(
|
|
1299
|
+
"src/my-component.tsx",
|
|
1300
|
+
declareOperation(gql`
|
|
1301
|
+
query UpdatedQuery($name: string!)
|
|
1302
|
+
@tool(name: "updated-tool", description: "Updated tool!") {
|
|
1303
|
+
updatedWorld(name: $name)
|
|
1304
|
+
}
|
|
1305
|
+
`)
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
server.watcher.emit("change", process.cwd() + "/src/my-component.tsx");
|
|
1309
|
+
|
|
1310
|
+
// Allow async handlers to complete
|
|
1311
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1312
|
+
|
|
1313
|
+
const manifestAfter = readManifestFile();
|
|
1314
|
+
expect(manifestAfter.operations).toHaveLength(1);
|
|
1315
|
+
expect(manifestAfter.operations[0].name).toBe("UpdatedQuery");
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
describe("html transforms", () => {
|
|
1320
|
+
test("replaces root relative scripts with full url when origin is provided", async () => {
|
|
1321
|
+
vol.fromJSON({
|
|
1322
|
+
"package.json": mockPackageJson(),
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
await using server = await setupServer({
|
|
1326
|
+
server: {
|
|
1327
|
+
origin: "http://localhost:3000",
|
|
1328
|
+
},
|
|
1329
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
const html = `<html><head><script type="module" src="/@vite/client"></script></head><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
|
|
1333
|
+
const result = await server.transformIndexHtml("index.html", html);
|
|
1334
|
+
|
|
1335
|
+
expect(result).toMatchInlineSnapshot(
|
|
1336
|
+
`
|
|
1337
|
+
"<html><head>
|
|
1338
|
+
<script type="module" src="http://localhost:3000/@vite/client"></script>
|
|
1339
|
+
<script type="module" src="http://localhost:3000/@vite/client"></script></head><body><script module src="http://localhost:3000/assets/main.ts?t=12345"></script></body></html>"
|
|
1340
|
+
`
|
|
1341
|
+
);
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
test("replaces root relative scripts with full url when origin is not provided", async () => {
|
|
1345
|
+
vol.fromJSON({
|
|
1346
|
+
"package.json": mockPackageJson(),
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
await using server = await setupServer({
|
|
1350
|
+
server: {
|
|
1351
|
+
port: 3000,
|
|
1352
|
+
},
|
|
1353
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
await server.listen();
|
|
1357
|
+
|
|
1358
|
+
const html = `<html><head> <script type="module">import { injectIntoGlobalHook } from "/@react-refresh";
|
|
1359
|
+
injectIntoGlobalHook(window);
|
|
1360
|
+
window.$RefreshReg$ = () => {};
|
|
1361
|
+
window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
|
|
1362
|
+
|
|
1363
|
+
const result = await server.transformIndexHtml("index.html", html);
|
|
1364
|
+
|
|
1365
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1366
|
+
"<html><head>
|
|
1367
|
+
<script type="module" src="http://localhost:3000/@vite/client"></script>
|
|
1368
|
+
<script type="module" src="http://localhost:3000/@id/__x00__index.html?html-proxy&index=0.js"></script></head></html>"
|
|
1369
|
+
`);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
test("replaces root relative imports with full url when origin is provided", async () => {
|
|
1373
|
+
vol.fromJSON({
|
|
1374
|
+
"package.json": mockPackageJson(),
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
await using server = await setupServer({
|
|
1378
|
+
server: {
|
|
1379
|
+
origin: "http://localhost:3000",
|
|
1380
|
+
},
|
|
1381
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
const html = `<html><head> <script type="module">import { injectIntoGlobalHook } from "/@react-refresh";
|
|
1385
|
+
injectIntoGlobalHook(window);
|
|
1386
|
+
window.$RefreshReg$ = () => {};
|
|
1387
|
+
window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
|
|
1388
|
+
|
|
1389
|
+
const result = await server.transformIndexHtml("index.html", html);
|
|
1390
|
+
|
|
1391
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1392
|
+
"<html><head>
|
|
1393
|
+
<script type="module" src="http://localhost:3000/@vite/client"></script>
|
|
1394
|
+
<script type="module" src="http://localhost:3000/@id/__x00__index.html?html-proxy&index=0.js"></script></head></html>"
|
|
1395
|
+
`);
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
test("replaces root relative imports with full url when origin is not provided", async () => {
|
|
1399
|
+
vol.fromJSON({
|
|
1400
|
+
"package.json": mockPackageJson(),
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
await using server = await setupServer({
|
|
1404
|
+
server: {
|
|
1405
|
+
port: 3000,
|
|
1406
|
+
},
|
|
1407
|
+
plugins: [apolloClientAiApps({ targets: ["mcp"] })],
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
await server.listen();
|
|
1411
|
+
|
|
1412
|
+
const html = `<html><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
|
|
1413
|
+
|
|
1414
|
+
const result = await server.transformIndexHtml("index.html", html);
|
|
1415
|
+
|
|
1416
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1417
|
+
"<html>
|
|
1418
|
+
<script type="module" src="http://localhost:3000/@vite/client"></script>
|
|
1419
|
+
<body><script module src="http://localhost:3000/assets/main.ts?t=12345"></script></body></html>"
|
|
1420
|
+
`);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
test("does not prepend absolute urls when running a build instead of a local server", async () => {
|
|
1424
|
+
vol.fromJSON({
|
|
1425
|
+
"package.json": mockPackageJson(),
|
|
1426
|
+
"index.html": `<html><head></head><body><script type="module" src="/src/main.ts"></script></body></html>`,
|
|
1427
|
+
"src/main.ts": "export default {};",
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
let transformedHtml: string | undefined;
|
|
1431
|
+
|
|
1432
|
+
await buildApp({
|
|
1433
|
+
build: { rollupOptions: { input: "index.html" } },
|
|
1434
|
+
server: {
|
|
1435
|
+
origin: "http://localhost:3000",
|
|
1436
|
+
},
|
|
1437
|
+
plugins: [
|
|
1438
|
+
apolloClientAiApps({ targets: ["mcp"] }),
|
|
1439
|
+
{
|
|
1440
|
+
name: "capture-html",
|
|
1441
|
+
transformIndexHtml: {
|
|
1442
|
+
order: "post",
|
|
1443
|
+
handler(html) {
|
|
1444
|
+
transformedHtml = html;
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
],
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
expect(transformedHtml).toMatchInlineSnapshot(`
|
|
1452
|
+
"<html><head> <script type="module" crossorigin src="/assets/index-B5Qt9EMX.js"></script>
|
|
1453
|
+
</head><body></body></html>"
|
|
1454
|
+
`);
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
function declareOperation(operation: DocumentNode) {
|
|
1459
|
+
const name = getOperationName(operation, "MY_OPERATION");
|
|
1460
|
+
const varName = name.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
|
|
1461
|
+
return `const ${varName} = gql\`\n${print(operation)}\n\``;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function mockPackageJson(config?: Record<string, unknown>) {
|
|
1465
|
+
return JSON.stringify({ version: "1.0.0", ...config });
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function readManifestFile(
|
|
1469
|
+
path = ".application-manifest.json"
|
|
1470
|
+
): ApplicationManifest {
|
|
1471
|
+
const manifest = JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
1472
|
+
|
|
1473
|
+
// Use a deterministic hash for tests to ensure snapshots maintain a
|
|
1474
|
+
// consistent output
|
|
1475
|
+
manifest.hash = "abc";
|
|
1476
|
+
|
|
1477
|
+
return manifest;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
async function tmpWriteRealFile(filepath: string, contents: string) {
|
|
1481
|
+
vi.doUnmock("node:fs");
|
|
1482
|
+
const fs = await import("node:fs");
|
|
1483
|
+
|
|
1484
|
+
fs.writeFileSync(filepath, contents);
|
|
1485
|
+
|
|
1486
|
+
return {
|
|
1487
|
+
[Symbol.dispose]() {
|
|
1488
|
+
fs.rmSync(filepath);
|
|
1489
|
+
},
|
|
1490
|
+
} satisfies Disposable;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function interceptWriteESMtoCJS() {
|
|
1494
|
+
// Cosmiconfig's async loadTs transpiles .ts configs to ES2022 module syntax
|
|
1495
|
+
// and writes a .mjs file. In vitest, the subsequent import() transforms ESM to
|
|
1496
|
+
// CJS differently than Node's native loader, causing the default export to be
|
|
1497
|
+
// double-wrapped as { __esModule: true, default: actualConfig }. Converting to
|
|
1498
|
+
// CJS module.exports before the file is written ensures correct behavior.
|
|
1499
|
+
const origWriteFile = fs.promises.writeFile.bind(fs.promises);
|
|
1500
|
+
(fs.promises as any).writeFile = async function (
|
|
1501
|
+
filepath: any,
|
|
1502
|
+
content: any,
|
|
1503
|
+
...args: any[]
|
|
1504
|
+
) {
|
|
1505
|
+
if (
|
|
1506
|
+
typeof filepath === "string" &&
|
|
1507
|
+
filepath.endsWith(".mjs") &&
|
|
1508
|
+
typeof content === "string"
|
|
1509
|
+
) {
|
|
1510
|
+
content = content.replace(/\bexport\s+default\s+/g, "module.exports = ");
|
|
1511
|
+
}
|
|
1512
|
+
return origWriteFile(filepath, content, ...args);
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
return {
|
|
1516
|
+
...fs.promises,
|
|
1517
|
+
[Symbol.dispose]() {
|
|
1518
|
+
fs.promises.writeFile = origWriteFile;
|
|
1519
|
+
},
|
|
1520
|
+
};
|
|
1521
|
+
}
|