mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 22:15:33 +00:00
feat(errors): P4 typed-error taxonomy adoption (0->91% locked) + erasable-syntax fix
Epic P4. Migrates plain throw new Error to the typed-error classes in the four locked subdomains, and makes the taxonomy erasable-syntax-compatible so it can be adopted across UI-reachable code. Migration (cliproxy/auth, web-server/routes, auth): - 21 of 23 throws in the locked subdomains now use typed subclasses (ProfileError, AuthError, ConfigError, ValidationError, ProviderError). - Typed adoption in locked subdomains: 0/23 -> 21/23 (91.3%), > 40% target. - Overall typed adoption: 0.9% -> 8.6%. - Messages preserved exactly (message-based tests stable). Exit codes now differentiate via handleError (ProfileError=7, AuthError=4, ConfigError=2, ProviderError=6). ccs doctor 0/1 contract untouched (outside scope). Erasable-syntax fix (unblocks the migration in the UI build graph): - exit-codes.ts: enum ExitCode -> const object + union type (value and type usage both preserved; no Object.values(ExitCode) consumers). - error-types.ts: constructor parameter properties -> explicit readonly field declarations + body assignment. - The web UI build enforces erasableSyntaxOnly (ui/tsconfig.app.json) and reaches src/errors via the @shared -> src/auth graph; pre-erasable error-types blocked the build once profile-registry adopted typed errors. Compat audit: docs/reports/typed-error-exit-code-compat-audit.md (Q1 resolved: migrate freely; only documented contract is ccs doctor, which is untouched). Behavior-lock: src/errors/__tests__/typed-error-migration-exit-codes.test.ts (taxonomy -> exit-code mapping, instanceof chains, context fields). validate + validate:ci-parity green (incl. UI build).
This commit is contained in:
1 parent
87aeb8f193
commit
7234ef8fcf
15 files changed
+256
-99
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
mutateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
import { normalizeSharedResourceMetadata, type SharedResourceMode } from './shared-resource-policy';
|
||||
import { ConfigError, ProfileError } from '../errors/error-types';
|
||||
|
||||
const logger = createLogger('auth:profile-registry');
|
||||
|
||||
@@ -132,7 +133,7 @@ export class ProfileRegistry {
|
||||
return JSON.parse(data) as ProfileData;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
throw new Error(`Failed to read profiles: ${message}`);
|
||||
throw new ConfigError(`Failed to read profiles: ${message}`, this.profilesPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ export class ProfileRegistry {
|
||||
fs.unlinkSync(tempPath);
|
||||
}
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
throw new Error(`Failed to write profiles: ${message}`);
|
||||
throw new ConfigError(`Failed to write profiles: ${message}`, this.profilesPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ export class ProfileRegistry {
|
||||
const data = this._read();
|
||||
|
||||
if (data.profiles[name]) {
|
||||
throw new Error(`Profile already exists: ${name}`);
|
||||
throw new ProfileError(`Profile already exists: ${name}`, name);
|
||||
}
|
||||
|
||||
// v3.0 minimal schema: only essential fields
|
||||
@@ -203,7 +204,7 @@ export class ProfileRegistry {
|
||||
const data = this._read();
|
||||
|
||||
if (!data.profiles[name]) {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
throw new ProfileError(`Profile not found: ${name}`, name);
|
||||
}
|
||||
|
||||
return this.normalizeLegacyProfileMetadata(data.profiles[name]);
|
||||
@@ -216,7 +217,7 @@ export class ProfileRegistry {
|
||||
const data = this._read();
|
||||
|
||||
if (!data.profiles[name]) {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
throw new ProfileError(`Profile not found: ${name}`, name);
|
||||
}
|
||||
|
||||
data.profiles[name] = this.normalizeLegacyProfileMetadata({
|
||||
@@ -234,7 +235,7 @@ export class ProfileRegistry {
|
||||
const data = this._read();
|
||||
|
||||
if (!data.profiles[name]) {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
throw new ProfileError(`Profile not found: ${name}`, name);
|
||||
}
|
||||
|
||||
delete data.profiles[name];
|
||||
@@ -287,7 +288,7 @@ export class ProfileRegistry {
|
||||
const data = this._read();
|
||||
|
||||
if (!data.profiles[name]) {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
throw new ProfileError(`Profile not found: ${name}`, name);
|
||||
}
|
||||
|
||||
data.default = name;
|
||||
@@ -330,7 +331,7 @@ export class ProfileRegistry {
|
||||
createAccountUnified(name: string, metadata: CreateMetadata = {}): void {
|
||||
mutateConfig((config) => {
|
||||
if (config.accounts[name]) {
|
||||
throw new Error(`Account already exists: ${name}`);
|
||||
throw new ProfileError(`Account already exists: ${name}`, name);
|
||||
}
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig({
|
||||
created: new Date().toISOString(),
|
||||
@@ -350,7 +351,7 @@ export class ProfileRegistry {
|
||||
updateAccountUnified(name: string, updates: Partial<AccountConfig>): void {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
throw new ProfileError(`Account not found: ${name}`, name);
|
||||
}
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig({
|
||||
...config.accounts[name],
|
||||
@@ -365,7 +366,7 @@ export class ProfileRegistry {
|
||||
removeAccountUnified(name: string): void {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
throw new ProfileError(`Account not found: ${name}`, name);
|
||||
}
|
||||
delete config.accounts[name];
|
||||
if (config.default === name) {
|
||||
@@ -382,7 +383,7 @@ export class ProfileRegistry {
|
||||
const exists =
|
||||
config.accounts[name] || config.profiles[name] || config.cliproxy?.variants?.[name];
|
||||
if (!exists) {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
throw new ProfileError(`Profile not found: ${name}`, name);
|
||||
}
|
||||
config.default = name;
|
||||
});
|
||||
@@ -434,7 +435,7 @@ export class ProfileRegistry {
|
||||
touchAccountUnified(name: string): void {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
throw new ProfileError(`Account not found: ${name}`, name);
|
||||
}
|
||||
config.accounts[name].last_used = new Date().toISOString();
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig(config.accounts[name]);
|
||||
|
||||
Reference in new issue
Block a user