mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
fix(cursor): export missing symbols, eliminate subcommand sync risk, improve tests
- Export detectProvider and formatModelName from barrel index - Export CURSOR_SUBCOMMANDS from cursor-command.ts, import in ccs.ts - Use getCcsDir() in tests instead of manual path construction - Add handleCursorCommand routing test for unknown subcommand
This commit is contained in:
+9
-7
@@ -536,13 +536,15 @@ async function main(): Promise<void> {
|
||||
// Only route to command handler for known subcommands, otherwise treat as profile
|
||||
// Note: Bare `ccs cursor` shows help (unlike copilot which falls through to profile)
|
||||
// This is intentional — cursor has no profile-switching mode
|
||||
// Keep in sync with cursor-command.ts switch cases
|
||||
const CURSOR_SUBCOMMANDS = ['auth', 'status', 'models', 'start', 'stop', 'help', '--help', '-h'];
|
||||
if (firstArg === 'cursor' && (args.length === 1 || CURSOR_SUBCOMMANDS.includes(args[1]))) {
|
||||
// `ccs cursor <subcommand>` - route to cursor command handler
|
||||
const { handleCursorCommand } = await import('./commands/cursor-command');
|
||||
const exitCode = await handleCursorCommand(args.slice(1));
|
||||
process.exit(exitCode);
|
||||
if (firstArg === 'cursor') {
|
||||
const { handleCursorCommand, CURSOR_SUBCOMMANDS } = await import('./commands/cursor-command');
|
||||
if (
|
||||
args.length === 1 ||
|
||||
CURSOR_SUBCOMMANDS.includes(args[1] as (typeof CURSOR_SUBCOMMANDS)[number])
|
||||
) {
|
||||
const exitCode = await handleCursorCommand(args.slice(1));
|
||||
process.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: copilot command (GitHub Copilot integration)
|
||||
|
||||
@@ -23,6 +23,18 @@ const DEFAULT_CURSOR_CONFIG = {
|
||||
model: DEFAULT_CURSOR_MODEL,
|
||||
};
|
||||
|
||||
/** Valid cursor subcommands — imported by ccs.ts for routing */
|
||||
export const CURSOR_SUBCOMMANDS = [
|
||||
'auth',
|
||||
'status',
|
||||
'models',
|
||||
'start',
|
||||
'stop',
|
||||
'help',
|
||||
'--help',
|
||||
'-h',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Handle cursor subcommand.
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,8 @@ export {
|
||||
fetchModelsFromDaemon,
|
||||
getAvailableModels,
|
||||
getDefaultModel,
|
||||
detectProvider,
|
||||
formatModelName,
|
||||
} from './cursor-models';
|
||||
|
||||
// Executor
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
stopDaemon,
|
||||
startDaemon,
|
||||
} from '../../../src/cursor/cursor-daemon';
|
||||
import { getCcsDir } from '../../../src/utils/config-manager';
|
||||
import { handleCursorCommand } from '../../../src/commands/cursor-command';
|
||||
|
||||
// Test isolation
|
||||
let originalCcsHome: string | undefined;
|
||||
@@ -41,8 +43,8 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// CCS_HOME is set to tempDir; getCcsDir() appends '.ccs' to it
|
||||
const ccsDir = () => path.join(tempDir, '.ccs');
|
||||
// Use getCcsDir() for consistent path resolution with production code
|
||||
const getTestCursorDir = () => path.join(getCcsDir(), 'cursor');
|
||||
|
||||
describe('getPidFromFile', () => {
|
||||
it('returns null when no PID file exists', () => {
|
||||
@@ -50,25 +52,25 @@ describe('getPidFromFile', () => {
|
||||
});
|
||||
|
||||
it('returns PID when valid PID file exists', () => {
|
||||
const cursorDir = path.join(ccsDir(), 'cursor');
|
||||
fs.mkdirSync(cursorDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(cursorDir, 'daemon.pid'), '12345');
|
||||
const dir = getTestCursorDir();
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'daemon.pid'), '12345');
|
||||
|
||||
expect(getPidFromFile()).toBe(12345);
|
||||
});
|
||||
|
||||
it('returns null when PID file contains invalid content', () => {
|
||||
const cursorDir = path.join(ccsDir(), 'cursor');
|
||||
fs.mkdirSync(cursorDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(cursorDir, 'daemon.pid'), 'not-a-number');
|
||||
const dir = getTestCursorDir();
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'daemon.pid'), 'not-a-number');
|
||||
|
||||
expect(getPidFromFile()).toBeNull();
|
||||
});
|
||||
|
||||
it('trims whitespace from PID file content', () => {
|
||||
const cursorDir = path.join(ccsDir(), 'cursor');
|
||||
fs.mkdirSync(cursorDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(cursorDir, 'daemon.pid'), ' 42 \n');
|
||||
const dir = getTestCursorDir();
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'daemon.pid'), ' 42 \n');
|
||||
|
||||
expect(getPidFromFile()).toBe(42);
|
||||
});
|
||||
@@ -78,25 +80,25 @@ describe('writePidToFile', () => {
|
||||
it('creates PID file with correct content', () => {
|
||||
writePidToFile(12345);
|
||||
|
||||
const pidFile = path.join(ccsDir(), 'cursor', 'daemon.pid');
|
||||
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
|
||||
expect(fs.existsSync(pidFile)).toBe(true);
|
||||
expect(fs.readFileSync(pidFile, 'utf8')).toBe('12345');
|
||||
});
|
||||
|
||||
it('creates cursor directory if it does not exist', () => {
|
||||
const cursorDir = path.join(ccsDir(), 'cursor');
|
||||
expect(fs.existsSync(cursorDir)).toBe(false);
|
||||
const dir = getTestCursorDir();
|
||||
expect(fs.existsSync(dir)).toBe(false);
|
||||
|
||||
writePidToFile(999);
|
||||
|
||||
expect(fs.existsSync(cursorDir)).toBe(true);
|
||||
expect(fs.existsSync(dir)).toBe(true);
|
||||
});
|
||||
|
||||
it('overwrites existing PID file', () => {
|
||||
writePidToFile(111);
|
||||
writePidToFile(222);
|
||||
|
||||
const pidFile = path.join(ccsDir(), 'cursor', 'daemon.pid');
|
||||
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
|
||||
expect(fs.readFileSync(pidFile, 'utf8')).toBe('222');
|
||||
});
|
||||
});
|
||||
@@ -104,7 +106,7 @@ describe('writePidToFile', () => {
|
||||
describe('removePidFile', () => {
|
||||
it('removes existing PID file', () => {
|
||||
writePidToFile(12345);
|
||||
const pidFile = path.join(ccsDir(), 'cursor', 'daemon.pid');
|
||||
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
|
||||
expect(fs.existsSync(pidFile)).toBe(true);
|
||||
|
||||
removePidFile();
|
||||
@@ -177,7 +179,14 @@ describe('stopDaemon', () => {
|
||||
expect(result.error).toBeUndefined();
|
||||
|
||||
// PID file should be removed
|
||||
const pidFile = path.join(ccsDir(), 'cursor', 'daemon.pid');
|
||||
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
|
||||
expect(fs.existsSync(pidFile)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCursorCommand', () => {
|
||||
it('returns exit code 1 for unknown subcommand', async () => {
|
||||
const exitCode = await handleCursorCommand(['nonexistent']);
|
||||
expect(exitCode).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user