fix(config): migrate all hardcoded paths to getCcsDir() and improve validation

- Replace os.homedir() + '.ccs' with getCcsDir() across 14 instances in 13 files:
  version-command, model-config, openrouter-catalog, config-checks, disk-cache,
  aggregator, auth-middleware, shell-completion, shared-manager, recovery-manager,
  auto-repair, delegation-validator, claude-dir-installer, cliproxy executor
- Add isDirectory() validation for --config-dir argument in ccs.ts
- Make detectCloudSyncPath() case-insensitive for cloud provider matching
- Fix help-command.ts to use dynamic dirDisplay for all path references
- Add 5 new tests: setGlobalConfigDir precedence, relative path resolution,
  reset behavior, getCcsDirSource with --config-dir, case-insensitive detection
- Clean up unused os imports after migration
This commit is contained in:
Tam Nhu Tran
2026-02-11 11:15:08 +07:00
parent 7a0e6a4112
commit 60d6bbd027
18 changed files with 87 additions and 59 deletions
+2 -2
View File
@@ -5,10 +5,10 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import { getCcsDir } from '../../utils/config-manager';
const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/models'; const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/models';
const CACHE_FILE = path.join(os.homedir(), '.ccs', 'openrouter-models-cache.json'); const CACHE_FILE = path.join(getCcsDir(), 'openrouter-models-cache.json');
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
export interface OpenRouterModel { export interface OpenRouterModel {
+2 -2
View File
@@ -286,8 +286,8 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
} }
if (!fs.existsSync(configDirValue)) { if (!fs.existsSync(configDirValue) || !fs.statSync(configDirValue).isDirectory()) {
console.error(fail(`Config directory not found: ${configDirValue}`)); console.error(fail(`Config directory not found or not a directory: ${configDirValue}`));
console.error(info('Create the directory first, then copy your config files into it.')); console.error(info('Create the directory first, then copy your config files into it.'));
process.exit(1); process.exit(1);
} }
+3 -1
View File
@@ -13,8 +13,10 @@
import { spawn, ChildProcess } from 'child_process'; import { spawn, ChildProcess } from 'child_process';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import { ProgressIndicator } from '../../utils/progress-indicator'; import { ProgressIndicator } from '../../utils/progress-indicator';
import { ok, fail, info, warn } from '../../utils/ui'; import { ok, fail, info, warn } from '../../utils/ui';
import { getCcsDir } from '../../utils/config-manager';
import { escapeShellArg } from '../../utils/shell-executor'; import { escapeShellArg } from '../../utils/shell-executor';
import { ensureCLIProxyBinary } from '../binary-manager'; import { ensureCLIProxyBinary } from '../binary-manager';
import { import {
@@ -655,7 +657,7 @@ export async function execClaudeWithCLIProxy(
upstreamBaseUrl: postSanitizationBaseUrl, upstreamBaseUrl: postSanitizationBaseUrl,
verbose, verbose,
defaultEffort: 'medium', defaultEffort: 'medium',
traceFilePath: traceEnabled ? `${os.homedir()}/.ccs/codex-reasoning-proxy.log` : '', traceFilePath: traceEnabled ? path.join(getCcsDir(), 'codex-reasoning-proxy.log') : '',
modelMap: { modelMap: {
defaultModel: initialEnvVars.ANTHROPIC_MODEL, defaultModel: initialEnvVars.ANTHROPIC_MODEL,
opusModel: initialEnvVars.ANTHROPIC_DEFAULT_OPUS_MODEL, opusModel: initialEnvVars.ANTHROPIC_DEFAULT_OPUS_MODEL,
+4 -6
View File
@@ -7,15 +7,12 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import { InteractivePrompt } from '../utils/prompt'; import { InteractivePrompt } from '../utils/prompt';
import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog'; import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog';
import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator'; import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
import { CLIProxyProvider } from './types'; import { CLIProxyProvider } from './types';
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui'; import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
import { getCcsDir } from '../utils/config-manager';
/** CCS directory */
const CCS_DIR = path.join(os.homedir(), '.ccs');
/** /**
* Check if provider has user settings configured * Check if provider has user settings configured
@@ -185,8 +182,9 @@ export async function configureProviderModel(
}; };
// Ensure CCS directory exists // Ensure CCS directory exists
if (!fs.existsSync(CCS_DIR)) { const ccsDir = getCcsDir();
fs.mkdirSync(CCS_DIR, { recursive: true }); if (!fs.existsSync(ccsDir)) {
fs.mkdirSync(ccsDir, { recursive: true });
} }
// Write settings file // Write settings file
+5 -3
View File
@@ -111,6 +111,10 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
); );
console.log(''); console.log('');
// Resolve display path for dynamic sections
const [dirSource] = getCcsDirSource();
const dirDisplay = dirSource === 'default' ? '~/.ccs' : getCcsDir();
// Usage section // Usage section
console.log(subheader('Usage:')); console.log(subheader('Usage:'));
console.log(` ${color('ccs', 'command')} [profile] [claude-args...]`); console.log(` ${color('ccs', 'command')} [profile] [claude-args...]`);
@@ -122,7 +126,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
printMajorSection( printMajorSection(
'API Key Profiles', 'API Key Profiles',
['Configure in ~/.ccs/*.settings.json'], [`Configure in ${dirDisplay}/*.settings.json`],
[ [
['ccs', 'Use default Claude account'], ['ccs', 'Use default Claude account'],
['ccs glm', 'GLM 4.6 (API key required)'], ['ccs glm', 'GLM 4.6 (API key required)'],
@@ -267,8 +271,6 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
]); ]);
// Configuration // Configuration
const [dirSource] = getCcsDirSource();
const dirDisplay = dirSource === 'default' ? '~/.ccs' : getCcsDir();
printConfigSection('Configuration', [ printConfigSection('Configuration', [
['Config File:', isUnifiedMode() ? `${dirDisplay}/config.yaml` : `${dirDisplay}/config.json`], ['Config File:', isUnifiedMode() ? `${dirDisplay}/config.yaml` : `${dirDisplay}/config.json`],
['Profiles:', `${dirDisplay}/profiles.json`], ['Profiles:', `${dirDisplay}/profiles.json`],
+5 -6
View File
@@ -6,9 +6,8 @@
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import { initUI, header, subheader, color, warn } from '../utils/ui'; import { initUI, header, subheader, color, warn } from '../utils/ui';
import { getActiveConfigPath } from '../utils/config-manager'; import { getActiveConfigPath, getCcsDir } from '../utils/config-manager';
import { getVersion } from '../utils/version'; import { getVersion } from '../utils/version';
/** /**
@@ -23,24 +22,24 @@ export async function handleVersionCommand(): Promise<void> {
const installLocation = process.argv[1] || '(not found)'; const installLocation = process.argv[1] || '(not found)';
console.log(` ${color('Location:'.padEnd(17), 'info')} ${installLocation}`); console.log(` ${color('Location:'.padEnd(17), 'info')} ${installLocation}`);
const ccsDir = path.join(os.homedir(), '.ccs'); const ccsDir = getCcsDir();
console.log(` ${color('CCS Directory:'.padEnd(17), 'info')} ${ccsDir}`); console.log(` ${color('CCS Directory:'.padEnd(17), 'info')} ${ccsDir}`);
const configPath = getActiveConfigPath(); const configPath = getActiveConfigPath();
console.log(` ${color('Config:'.padEnd(17), 'info')} ${configPath}`); console.log(` ${color('Config:'.padEnd(17), 'info')} ${configPath}`);
const profilesJson = path.join(os.homedir(), '.ccs', 'profiles.json'); const profilesJson = path.join(ccsDir, 'profiles.json');
console.log(` ${color('Profiles:'.padEnd(17), 'info')} ${profilesJson}`); console.log(` ${color('Profiles:'.padEnd(17), 'info')} ${profilesJson}`);
// Delegation status // Delegation status
const delegationSessionsPath = path.join(os.homedir(), '.ccs', 'delegation-sessions.json'); const delegationSessionsPath = path.join(ccsDir, 'delegation-sessions.json');
const delegationConfigured = fs.existsSync(delegationSessionsPath); const delegationConfigured = fs.existsSync(delegationSessionsPath);
const readyProfiles: string[] = []; const readyProfiles: string[] = [];
// Check for profiles with valid API keys // Check for profiles with valid API keys
for (const profile of ['glm', 'kimi']) { for (const profile of ['glm', 'kimi']) {
const settingsPath = path.join(os.homedir(), '.ccs', `${profile}.settings.json`); const settingsPath = path.join(ccsDir, `${profile}.settings.json`);
if (fs.existsSync(settingsPath)) { if (fs.existsSync(settingsPath)) {
try { try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
+2 -9
View File
@@ -6,8 +6,8 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os';
import { info } from '../utils/ui'; import { info } from '../utils/ui';
import { getCcsHome, getCcsDir } from '../utils/config-manager';
import { createEmptyUnifiedConfig, UNIFIED_CONFIG_VERSION } from '../config/unified-config-types'; import { createEmptyUnifiedConfig, UNIFIED_CONFIG_VERSION } from '../config/unified-config-types';
import { import {
saveUnifiedConfig, saveUnifiedConfig,
@@ -15,13 +15,6 @@ import {
loadUnifiedConfig, loadUnifiedConfig,
} from '../config/unified-config-loader'; } from '../config/unified-config-loader';
/**
* Get CCS home directory (respects CCS_HOME env for test isolation)
*/
function getCcsHome(): string {
return process.env.CCS_HOME || os.homedir();
}
/** /**
* Recovery Manager Class * Recovery Manager Class
*/ */
@@ -35,7 +28,7 @@ class RecoveryManager {
constructor() { constructor() {
this.homedir = getCcsHome(); this.homedir = getCcsHome();
this.ccsDir = path.join(this.homedir, '.ccs'); this.ccsDir = getCcsDir();
this.claudeDir = path.join(this.homedir, '.claude'); this.claudeDir = path.join(this.homedir, '.claude');
this.sharedDir = path.join(this.ccsDir, 'shared'); this.sharedDir = path.join(this.ccsDir, 'shared');
this.completionsDir = path.join(this.ccsDir, 'completions'); this.completionsDir = path.join(this.ccsDir, 'completions');
+2 -4
View File
@@ -4,8 +4,8 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os';
import { ok, warn, fail, info, header, color } from '../../utils/ui'; import { ok, warn, fail, info, header, color } from '../../utils/ui';
import { getCcsDir } from '../../utils/config-manager';
import { import {
CLIPROXY_DEFAULT_PORT, CLIPROXY_DEFAULT_PORT,
configNeedsRegeneration, configNeedsRegeneration,
@@ -28,8 +28,6 @@ const ora = createSpinner();
* 4. OAuth callback ports blocked by CLIProxy * 4. OAuth callback ports blocked by CLIProxy
*/ */
export async function runAutoRepair(): Promise<void> { export async function runAutoRepair(): Promise<void> {
const homedir = os.homedir();
console.log(''); console.log('');
console.log(header('AUTO-FIX MODE')); console.log(header('AUTO-FIX MODE'));
console.log(''); console.log('');
@@ -111,7 +109,7 @@ export async function runAutoRepair(): Promise<void> {
// Fix 4: Fix shared symlinks (settings.json broken by Claude CLI toggle thinking, etc.) // Fix 4: Fix shared symlinks (settings.json broken by Claude CLI toggle thinking, etc.)
const symlinkSpinner = ora('Checking shared settings.json symlink').start(); const symlinkSpinner = ora('Checking shared settings.json symlink').start();
const sharedSettings = path.join(homedir, '.ccs', 'shared', 'settings.json'); const sharedSettings = path.join(getCcsDir(), 'shared', 'settings.json');
try { try {
if (fs.existsSync(sharedSettings)) { if (fs.existsSync(sharedSettings)) {
const stats = fs.lstatSync(sharedSettings); const stats = fs.lstatSync(sharedSettings);
+1 -1
View File
@@ -62,7 +62,7 @@ class SharedManager {
const resolvedTarget = path.resolve(path.dirname(target), targetLink); const resolvedTarget = path.resolve(path.dirname(target), targetLink);
// Check if target points back to our shared dir or link path // Check if target points back to our shared dir or link path
const sharedDir = path.join(this.homeDir, '.ccs', 'shared'); const sharedDir = path.join(getCcsDir(), 'shared');
if (resolvedTarget.startsWith(sharedDir) || resolvedTarget === linkPath) { if (resolvedTarget.startsWith(sharedDir) || resolvedTarget === linkPath) {
console.log(warn(`Circular symlink detected: ${target}${resolvedTarget}`)); console.log(warn(`Circular symlink detected: ${target}${resolvedTarget}`));
return true; return true;
+1 -6
View File
@@ -197,12 +197,7 @@ export class ClaudeDirInstaller {
cleanupDeprecated(silent = false): CleanupResult { cleanupDeprecated(silent = false): CleanupResult {
const deprecatedFile = path.join(this.ccsClaudeDir, 'agents', 'ccs-delegator.md'); const deprecatedFile = path.join(this.ccsClaudeDir, 'agents', 'ccs-delegator.md');
const userSymlinkFile = path.join(this.homeDir, '.claude', 'agents', 'ccs-delegator.md'); const userSymlinkFile = path.join(this.homeDir, '.claude', 'agents', 'ccs-delegator.md');
const migrationMarker = path.join( const migrationMarker = path.join(getCcsDir(), '.migrations', 'v435-delegator-cleanup');
this.homeDir,
'.ccs',
'.migrations',
'v435-delegator-cleanup'
);
const cleanedFiles: string[] = []; const cleanedFiles: string[] = [];
+3 -3
View File
@@ -72,10 +72,10 @@ const CLOUD_SYNC_PATTERNS = [
* @returns Detected service name or null * @returns Detected service name or null
*/ */
export function detectCloudSyncPath(dir: string): string | null { export function detectCloudSyncPath(dir: string): string | null {
const normalized = dir.replace(/\\/g, '/'); const normalized = dir.replace(/\\/g, '/').toLowerCase();
for (const pattern of CLOUD_SYNC_PATTERNS) { for (const pattern of CLOUD_SYNC_PATTERNS) {
if (normalized.includes(pattern)) { if (normalized.includes(pattern.toLowerCase())) {
return pattern; return pattern; // Return original casing for display
} }
} }
return null; return null;
+3 -5
View File
@@ -2,9 +2,9 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os';
import { Settings } from '../types'; import { Settings } from '../types';
import { ValidationResult } from '../types/utils'; import { ValidationResult } from '../types/utils';
import { getCcsDir } from './config-manager';
/** /**
* Extended validation result for delegation profiles * Extended validation result for delegation profiles
@@ -28,8 +28,7 @@ export class DelegationValidator {
* @returns Validation result { valid: boolean, error?: string, settingsPath?: string } * @returns Validation result { valid: boolean, error?: string, settingsPath?: string }
*/ */
static validate(profileName: string): DelegationValidationResult { static validate(profileName: string): DelegationValidationResult {
const homeDir = os.homedir(); const settingsPath = path.join(getCcsDir(), `${profileName}.settings.json`);
const settingsPath = path.join(homeDir, '.ccs', `${profileName}.settings.json`);
// Check if profile directory exists // Check if profile directory exists
if (!fs.existsSync(settingsPath)) { if (!fs.existsSync(settingsPath)) {
@@ -144,8 +143,7 @@ export class DelegationValidator {
* @returns List of profile names ready for delegation * @returns List of profile names ready for delegation
*/ */
static getReadyProfiles(): string[] { static getReadyProfiles(): string[] {
const homeDir = os.homedir(); const ccsDir = getCcsDir();
const ccsDir = path.join(homeDir, '.ccs');
const configPath = path.join(ccsDir, 'config.yaml'); const configPath = path.join(ccsDir, 'config.yaml');
if (!fs.existsSync(ccsDir)) { if (!fs.existsSync(ccsDir)) {
+2 -1
View File
@@ -1,6 +1,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import { getCcsDir } from './config-manager';
// import { execSync } from 'child_process'; // Currently unused // import { execSync } from 'child_process'; // Currently unused
type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell' | null; type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell' | null;
@@ -24,7 +25,7 @@ export class ShellCompletionInstaller {
constructor() { constructor() {
this.homeDir = os.homedir(); this.homeDir = os.homedir();
this.ccsDir = path.join(this.homeDir, '.ccs'); this.ccsDir = getCcsDir();
this.completionDir = path.join(this.ccsDir, 'completions'); this.completionDir = path.join(this.ccsDir, 'completions');
this.scriptsDir = path.join(__dirname, '../../scripts/completion'); this.scriptsDir = path.join(__dirname, '../../scripts/completion');
} }
+2 -3
View File
@@ -6,9 +6,8 @@
*/ */
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { getConfigPath } from '../../utils/config-manager'; import { getConfigPath, getCcsDir } from '../../utils/config-manager';
import { isUnifiedMode, hasUnifiedConfig } from '../../config/unified-config-loader'; import { isUnifiedMode, hasUnifiedConfig } from '../../config/unified-config-loader';
import type { HealthCheck } from './types'; import type { HealthCheck } from './types';
@@ -18,7 +17,7 @@ import type { HealthCheck } from './types';
export function checkConfigFile(): HealthCheck { export function checkConfigFile(): HealthCheck {
// In unified mode, check config.yaml // In unified mode, check config.yaml
if (isUnifiedMode() || hasUnifiedConfig()) { if (isUnifiedMode() || hasUnifiedConfig()) {
const ccsDir = path.join(os.homedir(), '.ccs'); const ccsDir = getCcsDir();
const yamlPath = path.join(ccsDir, 'config.yaml'); const yamlPath = path.join(ccsDir, 'config.yaml');
if (!fs.existsSync(yamlPath)) { if (!fs.existsSync(yamlPath)) {
+2 -2
View File
@@ -10,7 +10,7 @@ import { getDashboardAuthConfig } from '../../config/unified-config-loader';
import crypto from 'crypto'; import crypto from 'crypto';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; import { getCcsDir } from '../../utils/config-manager';
// Extend Express Request with session // Extend Express Request with session
declare module 'express-session' { declare module 'express-session' {
@@ -24,7 +24,7 @@ declare module 'express-session' {
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/check', '/api/auth/setup', '/api/health']; const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/check', '/api/auth/setup', '/api/health'];
/** Path to persistent session secret file */ /** Path to persistent session secret file */
const SESSION_SECRET_PATH = path.join(os.homedir(), '.ccs', '.session-secret'); const SESSION_SECRET_PATH = path.join(getCcsDir(), '.session-secret');
/** /**
* Generate or retrieve persistent session secret. * Generate or retrieve persistent session secret.
+2 -2
View File
@@ -7,7 +7,6 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os';
import { import {
loadDailyUsageData, loadDailyUsageData,
loadMonthlyUsageData, loadMonthlyUsageData,
@@ -25,13 +24,14 @@ import {
getCacheAge, getCacheAge,
} from './disk-cache'; } from './disk-cache';
import { ok, info, fail } from '../../utils/ui'; import { ok, info, fail } from '../../utils/ui';
import { getCcsDir } from '../../utils/config-manager';
// ============================================================================ // ============================================================================
// Multi-Instance Support - Aggregate usage from CCS profiles // Multi-Instance Support - Aggregate usage from CCS profiles
// ============================================================================ // ============================================================================
/** Path to CCS instances directory */ /** Path to CCS instances directory */
const CCS_INSTANCES_DIR = path.join(os.homedir(), '.ccs', 'instances'); const CCS_INSTANCES_DIR = path.join(getCcsDir(), 'instances');
/** /**
* Get list of CCS instance paths that have usage data * Get list of CCS instance paths that have usage data
+2 -3
View File
@@ -10,13 +10,12 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os';
import type { DailyUsage, HourlyUsage, MonthlyUsage, SessionUsage } from './types'; import type { DailyUsage, HourlyUsage, MonthlyUsage, SessionUsage } from './types';
import { ok, info, warn } from '../../utils/ui'; import { ok, info, warn } from '../../utils/ui';
import { getCcsDir } from '../../utils/config-manager';
// Cache configuration // Cache configuration
const CCS_DIR = path.join(os.homedir(), '.ccs'); const CACHE_DIR = path.join(getCcsDir(), 'cache');
const CACHE_DIR = path.join(CCS_DIR, 'cache');
const CACHE_FILE = path.join(CACHE_DIR, 'usage.json'); const CACHE_FILE = path.join(CACHE_DIR, 'usage.json');
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const STALE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days (max age for stale data) const STALE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days (max age for stale data)
+44
View File
@@ -18,6 +18,9 @@ describe('Config Directory Override', function () {
originalCcsHome = process.env.CCS_HOME; originalCcsHome = process.env.CCS_HOME;
delete process.env.CCS_DIR; delete process.env.CCS_DIR;
delete process.env.CCS_HOME; delete process.env.CCS_HOME;
// Reset module-level state
const { setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir(undefined);
}); });
afterEach(function () { afterEach(function () {
@@ -25,6 +28,9 @@ describe('Config Directory Override', function () {
else delete process.env.CCS_DIR; else delete process.env.CCS_DIR;
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome; if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
else delete process.env.CCS_HOME; else delete process.env.CCS_HOME;
// Reset module-level state
const { setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir(undefined);
}); });
describe('getCcsDir() precedence', function () { describe('getCcsDir() precedence', function () {
@@ -52,6 +58,29 @@ describe('Config Directory Override', function () {
const { getCcsDir } = require('../../dist/utils/config-manager'); const { getCcsDir } = require('../../dist/utils/config-manager');
assert.strictEqual(getCcsDir(), path.resolve('/tmp/ccs-dir')); assert.strictEqual(getCcsDir(), path.resolve('/tmp/ccs-dir'));
}); });
it('should give setGlobalConfigDir() highest precedence', function () {
process.env.CCS_DIR = '/tmp/env-dir';
process.env.CCS_HOME = '/tmp/env-home';
const { getCcsDir, setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir('/tmp/flag-dir');
assert.strictEqual(getCcsDir(), path.resolve('/tmp/flag-dir'));
});
it('should resolve relative paths in setGlobalConfigDir()', function () {
const { getCcsDir, setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir('relative/path');
assert.strictEqual(getCcsDir(), path.resolve('relative/path'));
});
it('should clear override when setGlobalConfigDir(undefined) is called', function () {
const { getCcsDir, setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir('/tmp/override');
assert.strictEqual(getCcsDir(), path.resolve('/tmp/override'));
setGlobalConfigDir(undefined);
const expected = path.join(os.homedir(), '.ccs');
assert.strictEqual(getCcsDir(), expected);
});
}); });
describe('getCcsDirSource()', function () { describe('getCcsDirSource()', function () {
@@ -74,6 +103,14 @@ describe('Config Directory Override', function () {
const [source] = getCcsDirSource(); const [source] = getCcsDirSource();
assert.strictEqual(source, 'CCS_HOME'); assert.strictEqual(source, 'CCS_HOME');
}); });
it('should return "--config-dir" when setGlobalConfigDir() is set', function () {
const { getCcsDirSource, setGlobalConfigDir } = require('../../dist/utils/config-manager');
setGlobalConfigDir('/tmp/flag-test');
const [source, dir] = getCcsDirSource();
assert.strictEqual(source, '--config-dir');
assert.strictEqual(dir, path.resolve('/tmp/flag-test'));
});
}); });
describe('detectCloudSyncPath()', function () { describe('detectCloudSyncPath()', function () {
@@ -100,6 +137,13 @@ describe('Config Directory Override', function () {
assert.strictEqual(detectCloudSyncPath('/Users/kai/Google Drive/ccs'), 'Google Drive'); assert.strictEqual(detectCloudSyncPath('/Users/kai/Google Drive/ccs'), 'Google Drive');
}); });
it('should detect cloud paths case-insensitively', function () {
const { detectCloudSyncPath } = require('../../dist/utils/config-manager');
assert.strictEqual(detectCloudSyncPath('/Users/kai/dropbox/ccs'), 'Dropbox');
assert.strictEqual(detectCloudSyncPath('C:\\Users\\kai\\onedrive\\ccs'), 'OneDrive');
assert.strictEqual(detectCloudSyncPath('/Users/kai/google drive/ccs'), 'Google Drive');
});
it('should return null for regular paths', function () { it('should return null for regular paths', function () {
const { detectCloudSyncPath } = require('../../dist/utils/config-manager'); const { detectCloudSyncPath } = require('../../dist/utils/config-manager');
assert.strictEqual(detectCloudSyncPath('/home/kai/.ccs'), null); assert.strictEqual(detectCloudSyncPath('/home/kai/.ccs'), null);