mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 14:17:11 +00:00
fix: repair ccsx codex profile resources
This commit is contained in:
1 parent
17e221ac2c
commit
37a14525cc
9 files changed
+346
-17
No files matched your search
@@ -111,6 +111,19 @@ export async function main(argv: string[]): Promise<number> {
|
||||
`[!] codex-auth: shared config symlink failed (${msg}), continuing\n`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { ensureCodexProfileResources } =
|
||||
require('../codex-auth/codex-profile-resources') as {
|
||||
ensureCodexProfileResources: (dir: string) => void;
|
||||
};
|
||||
ensureCodexProfileResources(resolved.dir);
|
||||
} catch (resourceErr) {
|
||||
const msg = resourceErr instanceof Error ? resourceErr.message : String(resourceErr);
|
||||
process.stderr.write(
|
||||
`[!] codex-auth: shared resource repair failed (${msg}), continuing\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (resolverErr) {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { createLogger } from '../services/logging';
|
||||
|
||||
const logger = createLogger('codex-auth:resources');
|
||||
|
||||
export const SHARED_CODEX_RESOURCE_DIRS = ['agents', 'skills'] as const;
|
||||
|
||||
export interface EnsureCodexProfileResourcesOptions {
|
||||
sharedCodexHome?: string;
|
||||
}
|
||||
|
||||
export function ensureCodexProfileResources(
|
||||
profileDir: string,
|
||||
options: EnsureCodexProfileResourcesOptions = {}
|
||||
): void {
|
||||
const sharedCodexHome = options.sharedCodexHome ?? path.join(os.homedir(), '.codex');
|
||||
|
||||
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
||||
fs.mkdirSync(sharedCodexHome, { recursive: true, mode: 0o700 });
|
||||
|
||||
for (const resourceName of SHARED_CODEX_RESOURCE_DIRS) {
|
||||
ensureSharedResourceDir(profileDir, sharedCodexHome, resourceName);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSharedResourceDir(
|
||||
profileDir: string,
|
||||
sharedCodexHome: string,
|
||||
resourceName: (typeof SHARED_CODEX_RESOURCE_DIRS)[number]
|
||||
): void {
|
||||
const targetPath = path.join(sharedCodexHome, resourceName);
|
||||
const linkPath = path.join(profileDir, resourceName);
|
||||
|
||||
fs.mkdirSync(targetPath, { recursive: true, mode: 0o700 });
|
||||
|
||||
let existingStat: fs.Stats | null = null;
|
||||
try {
|
||||
existingStat = fs.lstatSync(linkPath);
|
||||
} catch {
|
||||
// Missing resource path: create it below.
|
||||
}
|
||||
|
||||
if (existingStat !== null) {
|
||||
if (existingStat.isSymbolicLink()) {
|
||||
const currentTarget = fs.readlinkSync(linkPath);
|
||||
if (currentTarget === targetPath) {
|
||||
return;
|
||||
}
|
||||
fs.unlinkSync(linkPath);
|
||||
} else if (existingStat.isDirectory()) {
|
||||
if (isDirectoryEmpty(linkPath)) {
|
||||
fs.rmSync(linkPath, { recursive: true, force: true });
|
||||
} else {
|
||||
copyMissingResourceEntries(targetPath, linkPath);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`[!] codex-auth: preserving existing non-directory ${resourceName} at ${linkPath}\n`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.symlinkSync(targetPath, linkPath, 'dir');
|
||||
logger.stage('dispatch', 'codex.resource.symlink.created', 'Created shared resource symlink', {
|
||||
link: linkPath,
|
||||
target: targetPath,
|
||||
resource: resourceName,
|
||||
});
|
||||
} catch (err) {
|
||||
fs.mkdirSync(linkPath, { recursive: true, mode: 0o700 });
|
||||
copyMissingResourceEntries(targetPath, linkPath);
|
||||
process.stderr.write(
|
||||
`[!] codex-auth: symlink unavailable; copied shared ${resourceName} to ${linkPath}. ` +
|
||||
`Resource edits won't propagate automatically.\n`
|
||||
);
|
||||
logger.warn(
|
||||
'codex-auth.resource-copy-fallback',
|
||||
'Copied shared resource after symlink failure',
|
||||
{
|
||||
link: linkPath,
|
||||
target: targetPath,
|
||||
resource: resourceName,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isDirectoryEmpty(dirPath: string): boolean {
|
||||
try {
|
||||
return fs.readdirSync(dirPath).length === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function copyMissingResourceEntries(sourceDir: string, targetDir: string): void {
|
||||
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
||||
const sourcePath = path.join(sourceDir, entry.name);
|
||||
const targetPath = path.join(targetDir, entry.name);
|
||||
if (fs.existsSync(targetPath)) {
|
||||
continue;
|
||||
}
|
||||
fs.cpSync(sourcePath, targetPath, {
|
||||
recursive: true,
|
||||
force: false,
|
||||
errorOnExist: false,
|
||||
preserveTimestamps: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,11 @@ import { createLogger } from '../../services/logging';
|
||||
import { initUI, info, ok } from '../../utils/ui';
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { resolveCodexProfileDir, ensureSharedConfigSymlink } from '../index';
|
||||
import {
|
||||
resolveCodexProfileDir,
|
||||
ensureSharedConfigSymlink,
|
||||
ensureCodexProfileResources,
|
||||
} from '../index';
|
||||
import { decodeAccountIdentity } from '../codex-account-identity';
|
||||
import { detectCodexCli } from '../../targets/codex-detector';
|
||||
import { parseArgs, rejectUnsupportedOptions, getProfileNameError } from './types';
|
||||
@@ -46,14 +50,14 @@ export async function handleCreateCodex(ctx: CodexCommandContext, args: string[]
|
||||
if (registry.hasProfile(profileName)) {
|
||||
if (force) {
|
||||
// --force: only re-link config.toml, preserve auth.json
|
||||
console.log(info(`Profile already exists: ${profileName} (re-linking config.toml)`));
|
||||
_ensureSymlinkSafe(profileDir, true);
|
||||
console.log(ok(`Profile config.toml re-linked.`));
|
||||
console.log(info(`Profile already exists: ${profileName} (repairing shared resources)`));
|
||||
_ensureProfileResourcesSafe(profileDir, true);
|
||||
console.log(ok(`Profile shared resources repaired.`));
|
||||
console.log(` Profile dir: ${profileDir}`);
|
||||
} else {
|
||||
_ensureSymlinkSafe(profileDir);
|
||||
_ensureProfileResourcesSafe(profileDir);
|
||||
console.log(info(`Profile already exists: ${profileName}`));
|
||||
console.log(ok(`Profile config.toml is ready.`));
|
||||
console.log(ok(`Profile shared resources are ready.`));
|
||||
console.log(` Profile dir: ${profileDir}`);
|
||||
console.log(` Run: ccsx auth login ${profileName}`);
|
||||
}
|
||||
@@ -64,7 +68,7 @@ export async function handleCreateCodex(ctx: CodexCommandContext, args: string[]
|
||||
// Avoids registry orphan if mkdir hits EACCES/ENOSPC.
|
||||
try {
|
||||
fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
|
||||
_ensureSymlinkSafe(profileDir);
|
||||
_ensureProfileResourcesSafe(profileDir);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
if ((err as NodeJS.ErrnoException).code === 'EACCES') {
|
||||
@@ -121,6 +125,20 @@ function _ensureSymlinkSafe(profileDir: string, overwriteRegularFile = false): v
|
||||
}
|
||||
}
|
||||
|
||||
function _ensureProfileResourcesSafe(profileDir: string, overwriteRegularFile = false): void {
|
||||
_ensureSymlinkSafe(profileDir, overwriteRegularFile);
|
||||
try {
|
||||
ensureCodexProfileResources(profileDir);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn('codex-auth.create.resource-repair-failed', 'Resource repair failed', {
|
||||
profileDir,
|
||||
error: msg,
|
||||
});
|
||||
exitWithError(`Failed to prepare profile resources: ${msg}`, ExitCode.CONFIG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function _spawnLogin(
|
||||
profileName: string,
|
||||
profileDir: string,
|
||||
|
||||
@@ -6,6 +6,7 @@ export {
|
||||
getSharedCodexConfigPath,
|
||||
} from './codex-profile-paths';
|
||||
export { ensureSharedConfigSymlink } from './codex-config-symlink';
|
||||
export { ensureCodexProfileResources, SHARED_CODEX_RESOURCE_DIRS } from './codex-profile-resources';
|
||||
export { decodeAccountIdentity } from './codex-account-identity';
|
||||
export { decodeIdToken } from './decode-id-token';
|
||||
export type { CodexProfileMetadata, CodexProfileData, CodexAccountIdentity } from './types';
|
||||
|
||||
Reference in new issue
Block a user