mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 20:20:09 +00:00
fix(cliproxy-sync): honor profile settings paths and targets
- resolve settings file from profile.settingsPath instead of reconstructing by name - skip non-claude targets for local claude-api-key sync semantics - add mapper regression tests for both behaviors
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
||||
import type { ClaudeKey } from '../management-api-types';
|
||||
|
||||
@@ -31,6 +32,15 @@ interface SettingsJson {
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
function resolveProfileSettingsPath(settingsPath: string): string {
|
||||
const normalized = settingsPath.replace(/\\/g, '/');
|
||||
if (normalized.startsWith('~/.ccs/')) {
|
||||
return path.join(getCcsDir(), normalized.slice('~/.ccs/'.length));
|
||||
}
|
||||
|
||||
return expandPath(settingsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load syncable API profiles from CCS config.
|
||||
* Filters to only configured profiles (with real API keys).
|
||||
@@ -45,9 +55,14 @@ export function loadSyncableProfiles(): SyncableProfile[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Local CLIProxy sync writes Claude-compatible entries only.
|
||||
// Profiles pinned to non-claude targets are intentionally skipped.
|
||||
if (profile.target !== 'claude') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load settings.json for env vars
|
||||
const ccsDir = getCcsDir();
|
||||
const settingsPath = path.join(ccsDir, `${profile.name}.settings.json`);
|
||||
const settingsPath = resolveProfileSettingsPath(profile.settingsPath);
|
||||
|
||||
let env: Record<string, string> | undefined;
|
||||
try {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
const fs = require('fs');
|
||||
|
||||
describe('Profile Mapper', () => {
|
||||
const profileMapper = require('../../../dist/cliproxy/sync/profile-mapper');
|
||||
const profileReader = require('../../../dist/api/services/profile-reader');
|
||||
|
||||
describe('mapProfileToClaudeKey', () => {
|
||||
it('returns null when env is missing', () => {
|
||||
@@ -86,6 +88,100 @@ describe('Profile Mapper', () => {
|
||||
const result = profileMapper.loadSyncableProfiles();
|
||||
assert.ok(Array.isArray(result));
|
||||
});
|
||||
|
||||
it('uses profile-provided settingsPath instead of reconstructing from profile name', () => {
|
||||
const originalListApiProfiles = profileReader.listApiProfiles;
|
||||
const originalExistsSync = fs.existsSync;
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
|
||||
const customSettingsPath = '/tmp/custom-sync-path.settings.json';
|
||||
const readPaths: string[] = [];
|
||||
|
||||
try {
|
||||
profileReader.listApiProfiles = () => ({
|
||||
profiles: [
|
||||
{
|
||||
name: 'glm',
|
||||
settingsPath: customSettingsPath,
|
||||
isConfigured: true,
|
||||
configSource: 'legacy',
|
||||
target: 'claude',
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
});
|
||||
|
||||
fs.existsSync = (filePath: string) => filePath === customSettingsPath;
|
||||
fs.readFileSync = (filePath: string) => {
|
||||
readPaths.push(filePath);
|
||||
return JSON.stringify({
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-test-key',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const result = profileMapper.loadSyncableProfiles();
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.strictEqual(result[0].settingsPath, customSettingsPath);
|
||||
assert.deepStrictEqual(readPaths, [customSettingsPath]);
|
||||
} finally {
|
||||
profileReader.listApiProfiles = originalListApiProfiles;
|
||||
fs.existsSync = originalExistsSync;
|
||||
fs.readFileSync = originalReadFileSync;
|
||||
}
|
||||
});
|
||||
|
||||
it('skips profiles pinned to non-claude targets during local sync mapping', () => {
|
||||
const originalListApiProfiles = profileReader.listApiProfiles;
|
||||
const originalExistsSync = fs.existsSync;
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
|
||||
const claudePath = '/tmp/claude-target.settings.json';
|
||||
const droidPath = '/tmp/droid-target.settings.json';
|
||||
const readPaths: string[] = [];
|
||||
|
||||
try {
|
||||
profileReader.listApiProfiles = () => ({
|
||||
profiles: [
|
||||
{
|
||||
name: 'claude-profile',
|
||||
settingsPath: claudePath,
|
||||
isConfigured: true,
|
||||
configSource: 'legacy',
|
||||
target: 'claude',
|
||||
},
|
||||
{
|
||||
name: 'droid-profile',
|
||||
settingsPath: droidPath,
|
||||
isConfigured: true,
|
||||
configSource: 'legacy',
|
||||
target: 'droid',
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
});
|
||||
|
||||
fs.existsSync = (filePath: string) => filePath === claudePath || filePath === droidPath;
|
||||
fs.readFileSync = (filePath: string) => {
|
||||
readPaths.push(filePath);
|
||||
return JSON.stringify({
|
||||
env: {
|
||||
ANTHROPIC_AUTH_TOKEN: 'sk-test-key',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const result = profileMapper.loadSyncableProfiles();
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.strictEqual(result[0].name, 'claude-profile');
|
||||
assert.deepStrictEqual(readPaths, [claudePath]);
|
||||
} finally {
|
||||
profileReader.listApiProfiles = originalListApiProfiles;
|
||||
fs.existsSync = originalExistsSync;
|
||||
fs.readFileSync = originalReadFileSync;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSyncPayload', () => {
|
||||
|
||||
Reference in New Issue
Block a user