mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 06:15:03 +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:
1 parent
2e8c7a3691
commit
ac1c744239
2 files changed
+113
-2
No files matched your search
@@ -7,6 +7,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getCcsDir } from '../../utils/config-manager';
|
import { getCcsDir } from '../../utils/config-manager';
|
||||||
|
import { expandPath } from '../../utils/helpers';
|
||||||
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
||||||
import type { ClaudeKey } from '../management-api-types';
|
import type { ClaudeKey } from '../management-api-types';
|
||||||
|
|
||||||
@@ -31,6 +32,15 @@ interface SettingsJson {
|
|||||||
env?: Record<string, string>;
|
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.
|
* Load syncable API profiles from CCS config.
|
||||||
* Filters to only configured profiles (with real API keys).
|
* Filters to only configured profiles (with real API keys).
|
||||||
@@ -45,9 +55,14 @@ export function loadSyncableProfiles(): SyncableProfile[] {
|
|||||||
continue;
|
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
|
// Load settings.json for env vars
|
||||||
const ccsDir = getCcsDir();
|
const settingsPath = resolveProfileSettingsPath(profile.settingsPath);
|
||||||
const settingsPath = path.join(ccsDir, `${profile.name}.settings.json`);
|
|
||||||
|
|
||||||
let env: Record<string, string> | undefined;
|
let env: Record<string, string> | undefined;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
describe('Profile Mapper', () => {
|
describe('Profile Mapper', () => {
|
||||||
const profileMapper = require('../../../dist/cliproxy/sync/profile-mapper');
|
const profileMapper = require('../../../dist/cliproxy/sync/profile-mapper');
|
||||||
|
const profileReader = require('../../../dist/api/services/profile-reader');
|
||||||
|
|
||||||
describe('mapProfileToClaudeKey', () => {
|
describe('mapProfileToClaudeKey', () => {
|
||||||
it('returns null when env is missing', () => {
|
it('returns null when env is missing', () => {
|
||||||
@@ -86,6 +88,100 @@ describe('Profile Mapper', () => {
|
|||||||
const result = profileMapper.loadSyncableProfiles();
|
const result = profileMapper.loadSyncableProfiles();
|
||||||
assert.ok(Array.isArray(result));
|
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', () => {
|
describe('generateSyncPayload', () => {
|
||||||
|
|||||||
Reference in new issue
Block a user