feat: Add Zed editor rule profile with agent rules and MCP config (#974)
* zed profile * add changeset * update changeset
This commit is contained in:
committed by
Ralph Khreish
parent
6c5e0f97f8
commit
b0e09c76ed
7
.changeset/metal-papers-stay.md
Normal file
7
.changeset/metal-papers-stay.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Add Zed editor rule profile with agent rules and MCP config
|
||||||
|
|
||||||
|
- Resolves #637
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
|
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
* - trae: Trae IDE rules
|
* - trae: Trae IDE rules
|
||||||
* - vscode: VS Code with GitHub Copilot integration
|
* - vscode: VS Code with GitHub Copilot integration
|
||||||
* - windsurf: Windsurf IDE rules
|
* - windsurf: Windsurf IDE rules
|
||||||
|
* - zed: Zed IDE rules
|
||||||
*
|
*
|
||||||
* To add a new rule profile:
|
* To add a new rule profile:
|
||||||
* 1. Add the profile name to this array
|
* 1. Add the profile name to this array
|
||||||
@@ -36,7 +37,8 @@ export const RULE_PROFILES = [
|
|||||||
'roo',
|
'roo',
|
||||||
'trae',
|
'trae',
|
||||||
'vscode',
|
'vscode',
|
||||||
'windsurf'
|
'windsurf',
|
||||||
|
'zed'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export { rooProfile } from './roo.js';
|
|||||||
export { traeProfile } from './trae.js';
|
export { traeProfile } from './trae.js';
|
||||||
export { vscodeProfile } from './vscode.js';
|
export { vscodeProfile } from './vscode.js';
|
||||||
export { windsurfProfile } from './windsurf.js';
|
export { windsurfProfile } from './windsurf.js';
|
||||||
|
export { zedProfile } from './zed.js';
|
||||||
|
|||||||
178
src/profiles/zed.js
Normal file
178
src/profiles/zed.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// Zed profile for rule-transformer
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { isSilentMode, log } from '../../scripts/modules/utils.js';
|
||||||
|
import { createProfile } from './base-profile.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform standard MCP config format to Zed format
|
||||||
|
* @param {Object} mcpConfig - Standard MCP configuration object
|
||||||
|
* @returns {Object} - Transformed Zed configuration object
|
||||||
|
*/
|
||||||
|
function transformToZedFormat(mcpConfig) {
|
||||||
|
const zedConfig = {};
|
||||||
|
|
||||||
|
// Transform mcpServers to context_servers
|
||||||
|
if (mcpConfig.mcpServers) {
|
||||||
|
zedConfig['context_servers'] = mcpConfig.mcpServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve any other existing settings
|
||||||
|
for (const [key, value] of Object.entries(mcpConfig)) {
|
||||||
|
if (key !== 'mcpServers') {
|
||||||
|
zedConfig[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return zedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle functions for Zed profile
|
||||||
|
function onAddRulesProfile(targetDir, assetsDir) {
|
||||||
|
// MCP transformation will be handled in onPostConvertRulesProfile
|
||||||
|
// File copying is handled by the base profile via fileMap
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemoveRulesProfile(targetDir) {
|
||||||
|
// Clean up .rules (Zed uses .rules directly in root)
|
||||||
|
const userRulesFile = path.join(targetDir, '.rules');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Remove Task Master .rules
|
||||||
|
if (fs.existsSync(userRulesFile)) {
|
||||||
|
fs.rmSync(userRulesFile, { force: true });
|
||||||
|
log('debug', `[Zed] Removed ${userRulesFile}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCP Removal: Remove context_servers section
|
||||||
|
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
|
||||||
|
|
||||||
|
if (!fs.existsSync(mcpConfigPath)) {
|
||||||
|
log('debug', '[Zed] No .zed/settings.json found to clean up');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read the current config
|
||||||
|
const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
|
||||||
|
const config = JSON.parse(configContent);
|
||||||
|
|
||||||
|
// Check if it has the context_servers section and task-master-ai server
|
||||||
|
if (
|
||||||
|
config['context_servers'] &&
|
||||||
|
config['context_servers']['task-master-ai']
|
||||||
|
) {
|
||||||
|
// Remove task-master-ai server
|
||||||
|
delete config['context_servers']['task-master-ai'];
|
||||||
|
|
||||||
|
// Check if there are other MCP servers in context_servers
|
||||||
|
const remainingServers = Object.keys(config['context_servers']);
|
||||||
|
|
||||||
|
if (remainingServers.length === 0) {
|
||||||
|
// No other servers, remove entire context_servers section
|
||||||
|
delete config['context_servers'];
|
||||||
|
log('debug', '[Zed] Removed empty context_servers section');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if config is now empty
|
||||||
|
const remainingKeys = Object.keys(config);
|
||||||
|
|
||||||
|
if (remainingKeys.length === 0) {
|
||||||
|
// Config is empty, remove entire file
|
||||||
|
fs.rmSync(mcpConfigPath, { force: true });
|
||||||
|
log('info', '[Zed] Removed empty settings.json file');
|
||||||
|
|
||||||
|
// Check if .zed directory is empty
|
||||||
|
const zedDirPath = path.join(targetDir, '.zed');
|
||||||
|
if (fs.existsSync(zedDirPath)) {
|
||||||
|
const remainingContents = fs.readdirSync(zedDirPath);
|
||||||
|
if (remainingContents.length === 0) {
|
||||||
|
fs.rmSync(zedDirPath, { recursive: true, force: true });
|
||||||
|
log('debug', '[Zed] Removed empty .zed directory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Write back the modified config
|
||||||
|
fs.writeFileSync(
|
||||||
|
mcpConfigPath,
|
||||||
|
JSON.stringify(config, null, '\t') + '\n'
|
||||||
|
);
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
'[Zed] Removed TaskMaster from settings.json, preserved other configurations'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log('debug', '[Zed] TaskMaster not found in context_servers');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log('error', `[Zed] Failed to clean up settings.json: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
||||||
|
// Handle .rules setup (same as onAddRulesProfile)
|
||||||
|
onAddRulesProfile(targetDir, assetsDir);
|
||||||
|
|
||||||
|
// Transform MCP config to Zed format
|
||||||
|
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
|
||||||
|
|
||||||
|
if (!fs.existsSync(mcpConfigPath)) {
|
||||||
|
log('debug', '[Zed] No .zed/settings.json found to transform');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read the generated standard MCP config
|
||||||
|
const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
|
||||||
|
const mcpConfig = JSON.parse(mcpConfigContent);
|
||||||
|
|
||||||
|
// Check if it's already in Zed format (has context_servers)
|
||||||
|
if (mcpConfig['context_servers']) {
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
'[Zed] settings.json already in Zed format, skipping transformation'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform to Zed format
|
||||||
|
const zedConfig = transformToZedFormat(mcpConfig);
|
||||||
|
|
||||||
|
// Write back the transformed config with proper formatting
|
||||||
|
fs.writeFileSync(
|
||||||
|
mcpConfigPath,
|
||||||
|
JSON.stringify(zedConfig, null, '\t') + '\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
log('info', '[Zed] Transformed settings.json to Zed format');
|
||||||
|
log('debug', '[Zed] Renamed mcpServers to context_servers');
|
||||||
|
} catch (error) {
|
||||||
|
log('error', `[Zed] Failed to transform settings.json: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and export zed profile using the base factory
|
||||||
|
export const zedProfile = createProfile({
|
||||||
|
name: 'zed',
|
||||||
|
displayName: 'Zed',
|
||||||
|
url: 'zed.dev',
|
||||||
|
docsUrl: 'zed.dev/docs',
|
||||||
|
profileDir: '.zed',
|
||||||
|
rulesDir: '.',
|
||||||
|
mcpConfig: true,
|
||||||
|
mcpConfigName: 'settings.json',
|
||||||
|
includeDefaultRules: false,
|
||||||
|
fileMap: {
|
||||||
|
'AGENTS.md': '.rules'
|
||||||
|
},
|
||||||
|
onAdd: onAddRulesProfile,
|
||||||
|
onRemove: onRemoveRulesProfile,
|
||||||
|
onPostConvert: onPostConvertRulesProfile
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export lifecycle functions separately to avoid naming conflicts
|
||||||
|
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
|
||||||
@@ -113,12 +113,12 @@ export async function runInteractiveProfilesSetup() {
|
|||||||
const hasMcpConfig = profile.mcpConfig === true;
|
const hasMcpConfig = profile.mcpConfig === true;
|
||||||
|
|
||||||
if (!profile.includeDefaultRules) {
|
if (!profile.includeDefaultRules) {
|
||||||
// Integration guide profiles (claude, codex, gemini, amp) - don't include standard coding rules
|
// Integration guide profiles (claude, codex, gemini, zed, amp) - don't include standard coding rules
|
||||||
if (profileName === 'claude') {
|
if (profileName === 'claude') {
|
||||||
description = 'Integration guide with Task Master slash commands';
|
description = 'Integration guide with Task Master slash commands';
|
||||||
} else if (profileName === 'codex') {
|
} else if (profileName === 'codex') {
|
||||||
description = 'Comprehensive Task Master integration guide';
|
description = 'Comprehensive Task Master integration guide';
|
||||||
} else if (profileName === 'gemini') {
|
} else if (profileName === 'gemini' || profileName === 'zed') {
|
||||||
description = 'Integration guide and MCP config';
|
description = 'Integration guide and MCP config';
|
||||||
} else if (profileName === 'amp') {
|
} else if (profileName === 'amp') {
|
||||||
description = 'Integration guide and MCP config';
|
description = 'Integration guide and MCP config';
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ describe('MCP Configuration Validation', () => {
|
|||||||
expectedDir: '.windsurf',
|
expectedDir: '.windsurf',
|
||||||
expectedConfigName: 'mcp.json',
|
expectedConfigName: 'mcp.json',
|
||||||
expectedPath: '.windsurf/mcp.json'
|
expectedPath: '.windsurf/mcp.json'
|
||||||
|
},
|
||||||
|
zed: {
|
||||||
|
shouldHaveMcp: true,
|
||||||
|
expectedDir: '.zed',
|
||||||
|
expectedConfigName: 'settings.json',
|
||||||
|
expectedPath: '.zed/settings.json'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
212
tests/unit/profiles/rule-transformer-zed.test.js
Normal file
212
tests/unit/profiles/rule-transformer-zed.test.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
// Mock fs module before importing anything that uses it
|
||||||
|
jest.mock('fs', () => ({
|
||||||
|
readFileSync: jest.fn(),
|
||||||
|
writeFileSync: jest.fn(),
|
||||||
|
existsSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import modules after mocking
|
||||||
|
import fs from 'fs';
|
||||||
|
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
|
||||||
|
import { zedProfile } from '../../../src/profiles/zed.js';
|
||||||
|
|
||||||
|
describe('Zed Rule Transformer', () => {
|
||||||
|
// Set up spies on the mocked modules
|
||||||
|
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
|
||||||
|
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
|
||||||
|
const mockExistsSync = jest.spyOn(fs, 'existsSync');
|
||||||
|
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
|
||||||
|
const mockConsoleError = jest
|
||||||
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
// Setup default mocks
|
||||||
|
mockReadFileSync.mockReturnValue('');
|
||||||
|
mockWriteFileSync.mockImplementation(() => {});
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
mockMkdirSync.mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly convert basic terms', () => {
|
||||||
|
const testContent = `---
|
||||||
|
description: Test Cursor rule for basic terms
|
||||||
|
globs: **/*
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
|
||||||
|
Also has references to .mdc files.`;
|
||||||
|
|
||||||
|
// Mock file read to return our test content
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
|
||||||
|
// Mock file system operations
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Get the transformed content
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify Cursor -> Zed transformations
|
||||||
|
expect(transformedContent).toContain('zed.dev');
|
||||||
|
expect(transformedContent).toContain('Zed');
|
||||||
|
expect(transformedContent).not.toContain('cursor.so');
|
||||||
|
expect(transformedContent).not.toContain('Cursor');
|
||||||
|
expect(transformedContent).toContain('.md');
|
||||||
|
expect(transformedContent).not.toContain('.mdc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle URL transformations', () => {
|
||||||
|
const testContent = `Visit https://cursor.so/docs for more information.
|
||||||
|
Also check out cursor.so and www.cursor.so for updates.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify URL transformations
|
||||||
|
expect(transformedContent).toContain('https://zed.dev');
|
||||||
|
expect(transformedContent).toContain('zed.dev');
|
||||||
|
expect(transformedContent).not.toContain('cursor.so');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file extension transformations', () => {
|
||||||
|
const testContent = `This rule references file.mdc and another.mdc file.
|
||||||
|
Use the .mdc extension for all rule files.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify file extension transformations
|
||||||
|
expect(transformedContent).toContain('file.md');
|
||||||
|
expect(transformedContent).toContain('another.md');
|
||||||
|
expect(transformedContent).toContain('.md extension');
|
||||||
|
expect(transformedContent).not.toContain('.mdc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case variations', () => {
|
||||||
|
const testContent = `CURSOR, Cursor, cursor should all be transformed.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify case transformations
|
||||||
|
// Due to regex order, the case-insensitive rule runs first:
|
||||||
|
// CURSOR -> Zed (because it starts with 'C'), Cursor -> Zed, cursor -> zed
|
||||||
|
expect(transformedContent).toContain('Zed');
|
||||||
|
expect(transformedContent).toContain('zed');
|
||||||
|
expect(transformedContent).not.toContain('CURSOR');
|
||||||
|
expect(transformedContent).not.toContain('Cursor');
|
||||||
|
expect(transformedContent).not.toContain('cursor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create target directory if it does not exist', () => {
|
||||||
|
const testContent = 'Test content';
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(false);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'nested/path/test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(mockMkdirSync).toHaveBeenCalledWith('nested/path', {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file system errors gracefully', () => {
|
||||||
|
mockReadFileSync.mockImplementation(() => {
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||||
|
'Error converting rule file: File not found'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle write errors gracefully', () => {
|
||||||
|
mockReadFileSync.mockReturnValue('Test content');
|
||||||
|
mockWriteFileSync.mockImplementation(() => {
|
||||||
|
throw new Error('Write permission denied');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
zedProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||||
|
'Error converting rule file: Write permission denied'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify profile configuration', () => {
|
||||||
|
expect(zedProfile.profileName).toBe('zed');
|
||||||
|
expect(zedProfile.displayName).toBe('Zed');
|
||||||
|
expect(zedProfile.profileDir).toBe('.zed');
|
||||||
|
expect(zedProfile.mcpConfig).toBe(true);
|
||||||
|
expect(zedProfile.mcpConfigName).toBe('settings.json');
|
||||||
|
expect(zedProfile.mcpConfigPath).toBe('.zed/settings.json');
|
||||||
|
expect(zedProfile.includeDefaultRules).toBe(false);
|
||||||
|
expect(zedProfile.fileMap).toEqual({
|
||||||
|
'AGENTS.md': '.rules'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,7 +22,8 @@ describe('Rule Transformer - General', () => {
|
|||||||
'roo',
|
'roo',
|
||||||
'trae',
|
'trae',
|
||||||
'vscode',
|
'vscode',
|
||||||
'windsurf'
|
'windsurf',
|
||||||
|
'zed'
|
||||||
];
|
];
|
||||||
expectedProfiles.forEach((profile) => {
|
expectedProfiles.forEach((profile) => {
|
||||||
expect(RULE_PROFILES).toContain(profile);
|
expect(RULE_PROFILES).toContain(profile);
|
||||||
@@ -229,6 +230,11 @@ describe('Rule Transformer - General', () => {
|
|||||||
mcpConfig: true,
|
mcpConfig: true,
|
||||||
mcpConfigName: 'mcp.json',
|
mcpConfigName: 'mcp.json',
|
||||||
expectedPath: '.windsurf/mcp.json'
|
expectedPath: '.windsurf/mcp.json'
|
||||||
|
},
|
||||||
|
zed: {
|
||||||
|
mcpConfig: true,
|
||||||
|
mcpConfigName: 'settings.json',
|
||||||
|
expectedPath: '.zed/settings.json'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
99
tests/unit/profiles/zed-integration.test.js
Normal file
99
tests/unit/profiles/zed-integration.test.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
|
// Mock external modules
|
||||||
|
jest.mock('child_process', () => ({
|
||||||
|
execSync: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock console methods
|
||||||
|
jest.mock('console', () => ({
|
||||||
|
log: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
clear: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Zed Integration', () => {
|
||||||
|
let tempDir;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Create a temporary directory for testing
|
||||||
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
|
||||||
|
|
||||||
|
// Spy on fs methods
|
||||||
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
||||||
|
if (filePath.toString().includes('settings.json')) {
|
||||||
|
return JSON.stringify({ context_servers: {} }, null, 2);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
});
|
||||||
|
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
||||||
|
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Clean up the temporary directory
|
||||||
|
try {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error cleaning up: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test function that simulates the createProjectStructure behavior for Zed files
|
||||||
|
function mockCreateZedStructure() {
|
||||||
|
// Create main .zed directory
|
||||||
|
fs.mkdirSync(path.join(tempDir, '.zed'), { recursive: true });
|
||||||
|
|
||||||
|
// Create MCP config file (settings.json)
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.zed', 'settings.json'),
|
||||||
|
JSON.stringify({ context_servers: {} }, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create AGENTS.md in project root
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, 'AGENTS.md'),
|
||||||
|
'# Task Master Instructions\n\nThis is the Task Master agents file.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('creates all required .zed directories', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateZedStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.zed'), {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates Zed settings.json with context_servers format', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateZedStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.zed', 'settings.json'),
|
||||||
|
JSON.stringify({ context_servers: {} }, null, 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates AGENTS.md in project root', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateZedStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, 'AGENTS.md'),
|
||||||
|
'# Task Master Instructions\n\nThis is the Task Master agents file.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user