mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat(ccw): migrate backend to TypeScript
- Convert 40 JS files to TypeScript (CLI, tools, core, MCP server) - Add Zod for runtime parameter validation - Add type definitions in src/types/ - Keep src/templates/ as JavaScript (dashboard frontend) - Update bin entries to use dist/ - Add tsconfig.json with strict mode - Add backward-compatible exports for tests - All 39 tests passing Breaking changes: None (backward compatible) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
177
ccw/src/tools/write-file.ts
Normal file
177
ccw/src/tools/write-file.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Write File Tool - Create or overwrite files
|
||||
*
|
||||
* Features:
|
||||
* - Create new files or overwrite existing
|
||||
* - Auto-create parent directories
|
||||
* - Support for text content with proper encoding
|
||||
* - Optional backup before overwrite
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { ToolSchema, ToolResult } from '../types/tool.js';
|
||||
import { writeFileSync, readFileSync, existsSync, mkdirSync, renameSync } from 'fs';
|
||||
import { resolve, isAbsolute, dirname, basename } from 'path';
|
||||
|
||||
// Define Zod schema for validation
|
||||
const ParamsSchema = z.object({
|
||||
path: z.string().min(1, 'Path is required'),
|
||||
content: z.string(),
|
||||
createDirectories: z.boolean().default(true),
|
||||
backup: z.boolean().default(false),
|
||||
encoding: z.enum(['utf8', 'utf-8', 'ascii', 'latin1', 'binary', 'hex', 'base64']).default('utf8'),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof ParamsSchema>;
|
||||
|
||||
interface WriteResult {
|
||||
success: boolean;
|
||||
path: string;
|
||||
created: boolean;
|
||||
overwritten: boolean;
|
||||
backupPath: string | null;
|
||||
bytes: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure parent directory exists
|
||||
* @param filePath - Path to file
|
||||
*/
|
||||
function ensureDir(filePath: string): void {
|
||||
const dir = dirname(filePath);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup of existing file
|
||||
* @param filePath - Path to file
|
||||
* @returns Backup path or null if no backup created
|
||||
*/
|
||||
function createBackup(filePath: string): string | null {
|
||||
if (!existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dir = dirname(filePath);
|
||||
const name = basename(filePath);
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = resolve(dir, `.${name}.${timestamp}.bak`);
|
||||
|
||||
try {
|
||||
const content = readFileSync(filePath);
|
||||
writeFileSync(backupPath, content);
|
||||
return backupPath;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create backup: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Tool schema for MCP
|
||||
export const schema: ToolSchema = {
|
||||
name: 'write_file',
|
||||
description: `Write content to file. Auto-creates parent directories.
|
||||
|
||||
Usage: write_file(path="file.js", content="code here")
|
||||
Options: backup=true (backup before overwrite), encoding="utf8"`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path to the file to create or overwrite',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Content to write to the file',
|
||||
},
|
||||
createDirectories: {
|
||||
type: 'boolean',
|
||||
description: 'Create parent directories if they do not exist (default: true)',
|
||||
default: true,
|
||||
},
|
||||
backup: {
|
||||
type: 'boolean',
|
||||
description: 'Create backup of existing file before overwriting (default: false)',
|
||||
default: false,
|
||||
},
|
||||
encoding: {
|
||||
type: 'string',
|
||||
description: 'File encoding (default: utf8)',
|
||||
default: 'utf8',
|
||||
enum: ['utf8', 'utf-8', 'ascii', 'latin1', 'binary', 'hex', 'base64'],
|
||||
},
|
||||
},
|
||||
required: ['path', 'content'],
|
||||
},
|
||||
};
|
||||
|
||||
// Handler function
|
||||
export async function handler(params: Record<string, unknown>): Promise<ToolResult<WriteResult>> {
|
||||
const parsed = ParamsSchema.safeParse(params);
|
||||
if (!parsed.success) {
|
||||
return { success: false, error: `Invalid params: ${parsed.error.message}` };
|
||||
}
|
||||
|
||||
const {
|
||||
path: filePath,
|
||||
content,
|
||||
createDirectories,
|
||||
backup,
|
||||
encoding,
|
||||
} = parsed.data;
|
||||
|
||||
// Resolve path
|
||||
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
||||
const fileExists = existsSync(resolvedPath);
|
||||
|
||||
// Create parent directories if needed
|
||||
if (createDirectories) {
|
||||
ensureDir(resolvedPath);
|
||||
} else if (!existsSync(dirname(resolvedPath))) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Parent directory does not exist: ${dirname(resolvedPath)}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Create backup if requested and file exists
|
||||
let backupPath: string | null = null;
|
||||
if (backup && fileExists) {
|
||||
try {
|
||||
backupPath = createBackup(resolvedPath);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Write file
|
||||
try {
|
||||
writeFileSync(resolvedPath, content, { encoding });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
success: true,
|
||||
path: resolvedPath,
|
||||
created: !fileExists,
|
||||
overwritten: fileExists,
|
||||
backupPath,
|
||||
bytes: Buffer.byteLength(content, encoding),
|
||||
message: fileExists
|
||||
? `Successfully overwrote ${filePath}${backupPath ? ` (backup: ${backupPath})` : ''}`
|
||||
: `Successfully created ${filePath}`,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to write file: ${(error as Error).message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user