mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(targets): run cleanup before adapter launch exits
- wrap adapter early-exit paths with a cleanup-aware exit helper - invoke cleanup before exiting on child spawn failures - validate Droid profile names in buildArgs and add unit coverage
This commit is contained in:
@@ -13,6 +13,7 @@ import { escapeShellArg, stripAnthropicEnv } from '../utils/shell-executor';
|
||||
import { ErrorManager } from '../utils/error-manager';
|
||||
import { getWebSearchHookEnv } from '../utils/websearch-manager';
|
||||
import { wireChildProcessSignals } from '../utils/signal-forwarder';
|
||||
import { runCleanup } from '../errors';
|
||||
|
||||
export class ClaudeAdapter implements TargetAdapter {
|
||||
readonly type: TargetType = 'claude';
|
||||
@@ -63,11 +64,19 @@ export class ClaudeAdapter implements TargetAdapter {
|
||||
env: NodeJS.ProcessEnv,
|
||||
_options?: { cwd?: string; binaryInfo?: TargetBinaryInfo }
|
||||
): void {
|
||||
const exitWithCleanup = (code: number): never => {
|
||||
try {
|
||||
runCleanup();
|
||||
} catch {
|
||||
// Cleanup should be best-effort on launch errors.
|
||||
}
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
const claudeCli = detectClaudeCli();
|
||||
if (!claudeCli) {
|
||||
void ErrorManager.showClaudeNotFound();
|
||||
process.exit(1);
|
||||
return;
|
||||
return exitWithCleanup(1);
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
@@ -118,7 +127,7 @@ export class ClaudeAdapter implements TargetAdapter {
|
||||
} else {
|
||||
console.error(`[X] Failed to start Claude CLI (${claudeCli}): ${err.message}`);
|
||||
}
|
||||
process.exit(1);
|
||||
return exitWithCleanup(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { ProfileType } from '../types/profile';
|
||||
import { upsertCcsModel } from './droid-config-manager';
|
||||
import { escapeShellArg } from '../utils/shell-executor';
|
||||
import { wireChildProcessSignals } from '../utils/signal-forwarder';
|
||||
import { runCleanup } from '../errors';
|
||||
|
||||
export class DroidAdapter implements TargetAdapter {
|
||||
readonly type: TargetType = 'droid';
|
||||
@@ -52,6 +53,11 @@ export class DroidAdapter implements TargetAdapter {
|
||||
}
|
||||
|
||||
buildArgs(profile: string, userArgs: string[]): string[] {
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
|
||||
throw new Error(
|
||||
`Invalid profile name "${profile}" for Droid target: only alphanumeric, underscore, hyphen allowed`
|
||||
);
|
||||
}
|
||||
return ['-m', `custom:ccs-${profile}`, ...userArgs];
|
||||
}
|
||||
|
||||
@@ -67,26 +73,32 @@ export class DroidAdapter implements TargetAdapter {
|
||||
env: NodeJS.ProcessEnv,
|
||||
options?: { cwd?: string; binaryInfo?: TargetBinaryInfo }
|
||||
): void {
|
||||
const exitWithCleanup = (code: number): never => {
|
||||
try {
|
||||
runCleanup();
|
||||
} catch {
|
||||
// Cleanup should be best-effort on launch errors.
|
||||
}
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
const droidPath = options?.binaryInfo?.path || detectDroidCli();
|
||||
if (!droidPath) {
|
||||
console.error('[X] Droid CLI not found. Install: npm i -g @factory/cli');
|
||||
process.exit(1);
|
||||
return;
|
||||
return exitWithCleanup(1);
|
||||
}
|
||||
try {
|
||||
const stat = fs.statSync(droidPath);
|
||||
if (!stat.isFile()) {
|
||||
console.error(`[X] Droid CLI path is not a file: ${droidPath}`);
|
||||
process.exit(1);
|
||||
return;
|
||||
return exitWithCleanup(1);
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
console.error(
|
||||
`[X] Droid CLI path is not accessible (${error.code || 'unknown'}): ${droidPath}`
|
||||
);
|
||||
process.exit(1);
|
||||
return;
|
||||
return exitWithCleanup(1);
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
@@ -138,7 +150,7 @@ export class DroidAdapter implements TargetAdapter {
|
||||
} else {
|
||||
console.error(`[X] Failed to start Droid CLI (${droidPath}):`, err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
return exitWithCleanup(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Unit tests for Droid adapter argument building.
|
||||
*/
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { DroidAdapter } from '../../../src/targets/droid-adapter';
|
||||
|
||||
describe('DroidAdapter.buildArgs', () => {
|
||||
it('builds droid model args for valid profile names', () => {
|
||||
const adapter = new DroidAdapter();
|
||||
expect(adapter.buildArgs('gemini_01', ['--help'])).toEqual(['-m', 'custom:ccs-gemini_01', '--help']);
|
||||
});
|
||||
|
||||
it('rejects unsafe profile names', () => {
|
||||
const adapter = new DroidAdapter();
|
||||
expect(() => adapter.buildArgs('bad profile', [])).toThrow(/Invalid profile name/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user