'use strict'; const fs = require('fs'); const path = require('path'); const { resolveProjectPath } = require('../path-safety'); const { refreshAssets } = require('./files'); const PRIMITIVE_TYPES = new Set(['Number', 'String', 'Boolean']); function generateComponentTemplate(className, properties = []) { const ccTypes = new Set(['Component']); const propLines = []; for (const prop of properties) { const propName = String(prop.name || 'property').trim(); const propType = String(prop.type || 'Number').trim(); const defaultValue = prop.default !== undefined ? prop.default : getDefaultForType(propType); if (!PRIMITIVE_TYPES.has(propType)) { ccTypes.add(propType); } if (PRIMITIVE_TYPES.has(propType)) { propLines.push(` @property`); propLines.push(` ${propName}: ${typeToTs(propType)} = ${defaultValue};`); } else { propLines.push(` @property(${propType})`); propLines.push(` ${propName}: ${propType} | null = null;`); } } const importTypes = Array.from(ccTypes).join(', '); const propsBlock = propLines.length > 0 ? propLines.join('\n') : ' // Add properties here'; return `import { _decorator, ${importTypes} } from 'cc'; const { ccclass, property } = _decorator; @ccclass('${className}') export class ${className} extends Component { ${propsBlock} start() { } update(deltaTime: number) { } } `; } function generatePlainTemplate(className) { return `export class ${className} { } `; } function getDefaultForType(type) { switch (type) { case 'Number': return '0'; case 'String': return "''"; case 'Boolean': return 'false'; default: return 'null'; } } function typeToTs(type) { switch (type) { case 'Number': return 'number'; case 'String': return 'string'; case 'Boolean': return 'boolean'; default: return type; } } function createScriptTools({ createSchema, getRuntimeContext }) { return [ { name: 'create_script', profile: 'full', description: '[core] Create a new TypeScript component script with a standard Cocos template.', inputSchema: createSchema( { scriptName: { type: 'string', description: 'Class name in PascalCase, e.g. PlayerController.' }, savePath: { type: 'string', description: 'Asset-db URL or project path, e.g. db://assets/scripts/PlayerController.ts.' }, template: { type: 'string', enum: ['component', 'plain'], description: 'Script template. Default: component.', }, properties: { type: 'array', items: { type: 'object' }, description: 'Optional @property declarations. Each item: { name, type, default }. Type can be Number, String, Boolean, or a CC class name like Label, Sprite, Node.', }, }, ['scriptName', 'savePath'] ), handler: async (args) => { const scriptName = String(args.scriptName || '').trim(); let savePath = String(args.savePath || '').trim(); if (!scriptName) { throw new Error('scriptName is required.'); } if (!savePath) { throw new Error('savePath is required.'); } if (!savePath.endsWith('.ts')) { throw new Error('savePath must end with .ts'); } if (savePath.startsWith('db://')) { savePath = savePath.slice('db://'.length); } const templateType = args.template === 'plain' ? 'plain' : 'component'; const properties = Array.isArray(args.properties) ? args.properties : []; const content = templateType === 'plain' ? generatePlainTemplate(scriptName) : generateComponentTemplate(scriptName, properties); const { projectPath } = getRuntimeContext(); const fullPath = resolveProjectPath(projectPath, savePath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, content, 'utf8'); const refreshMessage = await refreshAssets(projectPath, fullPath); return { created: true, scriptName, savePath, template: templateType, className: scriptName, contentLength: content.length, refreshMessage, }; }, }, ]; } module.exports = { createScriptTools, generateComponentTemplate, generatePlainTemplate, };