fix(ci): repair validate and windows checks

This commit is contained in:
777genius 2026-04-17 09:12:49 +03:00
parent f92b77e3af
commit a5c79518fb
4 changed files with 50 additions and 47 deletions

View file

@ -125,13 +125,7 @@ export function hasInstallationInScope(
return installations.some((installation) => installation.scope === scope);
}
/**
* Build a concise install-status label for plugin badges.
*/
export function getInstallationSummaryLabel(
installations: Pick<InstalledPluginEntry, 'scope'>[]
): string | null {
const scopes = Array.from(new Set(installations.map((installation) => installation.scope)));
function summarizeInstallationScopes(scopes: InstallScope[]): string | null {
if (scopes.length === 0) {
return null;
}
@ -152,6 +146,16 @@ export function getInstallationSummaryLabel(
}
}
/**
* Build a concise install-status label for plugin badges.
*/
export function getInstallationSummaryLabel(
installations: Pick<InstalledPluginEntry, 'scope'>[]
): string | null {
const scopes = Array.from(new Set(installations.map((installation) => installation.scope)));
return summarizeInstallationScopes(scopes);
}
const MCP_SCOPE_PRIORITY: Record<InstalledMcpEntry['scope'], number> = {
local: 0,
project: 1,
@ -181,24 +185,7 @@ export function getMcpInstallationSummaryLabel(
installations: Pick<InstalledMcpEntry, 'scope'>[]
): string | null {
const scopes = Array.from(new Set(installations.map((installation) => installation.scope)));
if (scopes.length === 0) {
return null;
}
if (scopes.length > 1) {
return `Installed in ${scopes.length} scopes`;
}
switch (scopes[0]) {
case 'user':
return 'Installed globally';
case 'project':
return 'Installed in project';
case 'local':
return 'Installed locally';
default:
return 'Installed';
}
return summarizeInstallationScopes(scopes);
}
/**

View file

@ -1,10 +1,20 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { McpInstallationStateService } from '@main/services/extensions/state/McpInstallationStateService';
const TEST_ROOT = path.parse(process.cwd()).root || path.sep;
const MOCK_HOME_PATH = path.join(TEST_ROOT, 'tmp', 'mock-home');
const PROJECT_A_PATH = path.join(TEST_ROOT, 'tmp', 'project-a');
const PROJECT_B_PATH = path.join(TEST_ROOT, 'tmp', 'project-b');
function normalizeMockPath(filePath: unknown): string {
return String(filePath).replaceAll('\\', '/');
}
vi.mock('@main/utils/pathDecoder', () => ({
getHomeDir: () => '/tmp/mock-home',
getHomeDir: () => MOCK_HOME_PATH,
}));
vi.mock('node:fs/promises');
@ -25,14 +35,14 @@ describe('McpInstallationStateService', () => {
describe('getInstalled', () => {
it('includes local scope from the current project entry in ~/.claude.json', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath);
if (normalizedPath === '/tmp/mock-home/.claude.json') {
const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath === normalizeMockPath(path.join(MOCK_HOME_PATH, '.claude.json'))) {
return JSON.stringify({
mcpServers: {
context7: { command: 'npx -y @upstash/context7-mcp' },
},
projects: {
'/tmp/project-a': {
[PROJECT_A_PATH]: {
mcpServers: {
stripe: { url: 'https://mcp.stripe.com' },
},
@ -41,7 +51,7 @@ describe('McpInstallationStateService', () => {
});
}
if (normalizedPath === '/tmp/project-a/.mcp.json') {
if (normalizedPath === normalizeMockPath(path.join(PROJECT_A_PATH, '.mcp.json'))) {
return JSON.stringify({
mcpServers: {
paypal: { url: 'https://mcp.paypal.com/mcp' },
@ -52,7 +62,7 @@ describe('McpInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
});
const entries = await service.getInstalled('/tmp/project-a');
const entries = await service.getInstalled(PROJECT_A_PATH);
expect(entries).toEqual([
{ name: 'context7', scope: 'user', transport: 'stdio' },
@ -63,8 +73,8 @@ describe('McpInstallationStateService', () => {
it('caches results within TTL for the same project path', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath);
if (normalizedPath === '/tmp/mock-home/.claude.json') {
const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath === normalizeMockPath(path.join(MOCK_HOME_PATH, '.claude.json'))) {
return JSON.stringify({
mcpServers: {
context7: { command: 'npx -y @upstash/context7-mcp' },
@ -72,7 +82,7 @@ describe('McpInstallationStateService', () => {
});
}
if (normalizedPath === '/tmp/project-a/.mcp.json') {
if (normalizedPath === normalizeMockPath(path.join(PROJECT_A_PATH, '.mcp.json'))) {
return JSON.stringify({
mcpServers: {
'repo-a-server': { url: 'https://repo-a.example.com/mcp' },
@ -83,27 +93,27 @@ describe('McpInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
});
await service.getInstalled('/tmp/project-a');
await service.getInstalled('/tmp/project-a');
await service.getInstalled(PROJECT_A_PATH);
await service.getInstalled(PROJECT_A_PATH);
expect(mockedFs.readFile).toHaveBeenCalledTimes(2);
});
it('caches results independently per project path', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath);
if (normalizedPath === '/tmp/mock-home/.claude.json') {
const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath === normalizeMockPath(path.join(MOCK_HOME_PATH, '.claude.json'))) {
return JSON.stringify({
mcpServers: {
context7: { command: 'npx -y @upstash/context7-mcp' },
},
projects: {
'/tmp/project-a': {
[PROJECT_A_PATH]: {
mcpServers: {
stripe: { url: 'https://mcp.stripe.com' },
},
},
'/tmp/project-b': {
[PROJECT_B_PATH]: {
mcpServers: {
github: { command: 'uvx github-mcp' },
},
@ -112,7 +122,7 @@ describe('McpInstallationStateService', () => {
});
}
if (normalizedPath === '/tmp/project-a/.mcp.json') {
if (normalizedPath === normalizeMockPath(path.join(PROJECT_A_PATH, '.mcp.json'))) {
return JSON.stringify({
mcpServers: {
'repo-a-server': { url: 'https://repo-a.example.com/mcp' },
@ -120,7 +130,7 @@ describe('McpInstallationStateService', () => {
});
}
if (normalizedPath === '/tmp/project-b/.mcp.json') {
if (normalizedPath === normalizeMockPath(path.join(PROJECT_B_PATH, '.mcp.json'))) {
return JSON.stringify({
mcpServers: {
'repo-b-server': { command: 'uvx repo-b-mcp' },
@ -131,8 +141,8 @@ describe('McpInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
});
const projectAEntries = await service.getInstalled('/tmp/project-a');
const projectBEntries = await service.getInstalled('/tmp/project-b');
const projectAEntries = await service.getInstalled(PROJECT_A_PATH);
const projectBEntries = await service.getInstalled(PROJECT_B_PATH);
expect(projectAEntries).toEqual([
{ name: 'context7', scope: 'user', transport: 'stdio' },

View file

@ -9,7 +9,7 @@ const MOCK_CLAUDE_BASE_PATH = path.join(TEST_ROOT, 'tmp', 'mock-claude');
const PROJECT_A_PATH = path.join(TEST_ROOT, 'tmp', 'project-a');
const PROJECT_B_PATH = path.join(TEST_ROOT, 'tmp', 'project-b');
function normalizeMockPath(filePath: string | URL): string {
function normalizeMockPath(filePath: unknown): string {
return String(filePath).replaceAll('\\', '/');
}

View file

@ -257,9 +257,15 @@ describe('PluginDetailDialog project context', () => {
});
const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
const projectOption = scopeSelect.querySelector(
'option[value="project"]'
) as HTMLOptionElement | null;
const localOption = scopeSelect.querySelector(
'option[value="local"]'
) as HTMLOptionElement | null;
expect(scopeSelect).not.toBeNull();
expect(scopeSelect.querySelector('option[value="project"]')?.disabled).toBe(true);
expect(scopeSelect.querySelector('option[value="local"]')?.disabled).toBe(true);
expect(projectOption?.disabled).toBe(true);
expect(localOption?.disabled).toBe(true);
await act(async () => {
root.unmount();