PluginsHow-To Guides
Modify Generated Files
Read and modify files in the generated directory
Modify Generated Files
Read and modify files in the generated project directory. Plugins have full filesystem access.
Basic File Operations
Read a File
plugin.ts
import { StartPluginWithLambda, type CyanPluginInput, type PluginOutput } from '@atomicloud/cyan-sdk';import * as fs from 'fs/promises';import * as path from 'path';StartPluginWithLambda(async (input: CyanPluginInput): Promise<PluginOutput> => {const { directory } = input;// Read fileconst readmePath = path.join(directory, 'README.md');const content = await fs.readFile(readmePath, 'utf-8');console.log('README content:', content);return { directory };});
plugin.py
from cyan_sdk import start_plugin_with_fn, CyanPluginInput, PluginOutputimport os@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directory# Read filereadme_path = os.path.join(directory, 'README.md')with open(readme_path, 'r', encoding='utf-8') as f:content = f.read()print(f'README content: {content}')return {'directory': directory}
Plugin.cs
using CyanSdk;using System.IO;[PluginMain]public static class Plugin{public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;// Read filevar readmePath = Path.Combine(directory, "README.md");var content = await File.ReadAllTextAsync(readmePath);Console.WriteLine($"README content: {content}");return new PluginOutput { Directory = directory };}}
Write a File
plugin.ts
StartPluginWithLambda(async (input) => {const { directory } = input;const readmePath = path.join(directory, 'README.md');await fs.writeFile(readmePath, '# My Project\n\nGenerated by CyanPrint');return { directory };});
plugin.py
@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directoryreadme_path = os.path.join(directory, 'README.md')with open(readme_path, 'w', encoding='utf-8') as f:f.write('# My Project\n\nGenerated by CyanPrint')return {'directory': directory}
Plugin.cs
[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;var readmePath = Path.Combine(directory, "README.md");await File.WriteAllTextAsync(readmePath, "# My Project\n\nGenerated by CyanPrint");return new PluginOutput { Directory = directory };}
Modify a File
plugin.ts
StartPluginWithLambda(async (input) => {const { directory } = input;const readmePath = path.join(directory, 'README.md');// Readlet content = await fs.readFile(readmePath, 'utf-8');// Modifycontent = content + '\n\n## Generated\n\nThis project was generated by CyanPrint.';// Write backawait fs.writeFile(readmePath, content);return { directory };});
plugin.py
@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directoryreadme_path = os.path.join(directory, 'README.md')# Readwith open(readme_path, 'r', encoding='utf-8') as f:content = f.read()# Modifycontent = content + '\n\n## Generated\n\nThis project was generated by CyanPrint.'# Write backwith open(readme_path, 'w', encoding='utf-8') as f:f.write(content)return {'directory': directory}
Plugin.cs
[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;var readmePath = Path.Combine(directory, "README.md");// Readvar content = await File.ReadAllTextAsync(readmePath);// Modifycontent += "\n\n## Generated\n\nThis project was generated by CyanPrint.";// Write backawait File.WriteAllTextAsync(readmePath, content);return new PluginOutput { Directory = directory };}
Common Patterns
Update JSON File
plugin.ts
StartPluginWithLambda(async (input) => {const { directory } = input;// Read package.jsonconst pkgPath = path.join(directory, 'package.json');const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));// Modifypkg.scripts = pkg.scripts || {};pkg.scripts.dev = 'next dev --turbo';pkg.scripts.build = 'next build';// Add dependenciespkg.dependencies = pkg.dependencies || {};pkg.dependencies['lodash'] = '^4.17.21';// Write backawait fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));return { directory };});
plugin.py
import json@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directory# Read package.jsonpkg_path = os.path.join(directory, 'package.json')with open(pkg_path, 'r', encoding='utf-8') as f:pkg = json.load(f)# Modifypkg.setdefault('scripts', {})pkg['scripts']['dev'] = 'next dev --turbo'pkg['scripts']['build'] = 'next build'# Add dependenciespkg.setdefault('dependencies', {})pkg['dependencies']['lodash'] = '^4.17.21'# Write backwith open(pkg_path, 'w', encoding='utf-8') as f:json.dump(pkg, f, indent=2)return {'directory': directory}
Plugin.cs
using System.Text.Json;[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;// Read package.jsonvar pkgPath = Path.Combine(directory, "package.json");var jsonContent = await File.ReadAllTextAsync(pkgPath);var pkg = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonContent);// Modifyvar scripts = new Dictionary<string, string>{["dev"] = "next dev --turbo",["build"] = "next build"};pkg["scripts"] = scripts;// Add dependenciesvar dependencies = new Dictionary<string, string>{["lodash"] = "^4.17.21"};pkg["dependencies"] = dependencies;// Write backvar options = new JsonSerializerOptions { WriteIndented = true };await File.WriteAllTextAsync(pkgPath, JsonSerializer.Serialize(pkg, options));return new PluginOutput { Directory = directory };}
Add to Existing File
plugin.ts
StartPluginWithLambda(async (input) => {const { directory, config } = input;const cfg = config as { author?: string };const readmePath = path.join(directory, 'README.md');// Check if file existstry {let content = await fs.readFile(readmePath, 'utf-8');// Add author line if providedif (cfg.author) {content = content.replace('# ',`# Project\n\nAuthor: ${cfg.author}\n\n`);await fs.writeFile(readmePath, content);}} catch {// File doesn't exist, create itawait fs.writeFile(readmePath, `# Project\n\nAuthor: ${cfg.author || 'Unknown'}\n`);}return { directory };});
plugin.py
@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directorycfg = input.config or {}author = cfg.get('author')readme_path = os.path.join(directory, 'README.md')# Check if file existstry:with open(readme_path, 'r', encoding='utf-8') as f:content = f.read()# Add author line if providedif author:content = content.replace('# ',f'# Project\n\nAuthor: {author}\n\n')with open(readme_path, 'w', encoding='utf-8') as f:f.write(content)except FileNotFoundError:# File doesn't exist, create itwith open(readme_path, 'w', encoding='utf-8') as f:f.write(f'# Project\n\nAuthor: {author or "Unknown"}\n')return {'directory': directory}
Plugin.cs
[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;var cfg = input.Config as Dictionary<string, object>;var author = cfg?.GetValueOrDefault("author")?.ToString();var readmePath = Path.Combine(directory, "README.md");// Check if file existstry{var content = await File.ReadAllTextAsync(readmePath);// Add author line if providedif (author != null){content = content.Replace("# ",$"# Project\n\nAuthor: {author}\n\n");await File.WriteAllTextAsync(readmePath, content);}}catch (FileNotFoundException){// File doesn't exist, create itawait File.WriteAllTextAsync(readmePath, $"# Project\n\nAuthor: {author ?? "Unknown"}\n");}return new PluginOutput { Directory = directory };}
Create New Files
.gitignore
.env.example
plugin.ts
StartPluginWithLambda(async (input) => {const { directory } = input;// Create .gitignoreawait fs.writeFile(path.join(directory, '.gitignore'),`node_modules/dist/.env*.log`);// Create .env.exampleawait fs.writeFile(path.join(directory, '.env.example'),`DATABASE_URL=postgres://localhost:5432/mydbAPI_KEY=your-api-key`);// Create directory and fileconst githubDir = path.join(directory, '.github', 'workflows');await fs.mkdir(githubDir, { recursive: true });await fs.writeFile(path.join(githubDir, 'ci.yml'),`name: CIon: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- run: npm ci- run: npm test`);return { directory };});
plugin.py
@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directory# Create .gitignorewith open(os.path.join(directory, '.gitignore'), 'w', encoding='utf-8') as f:f.write('''node_modules/dist/.env*.log''')# Create .env.examplewith open(os.path.join(directory, '.env.example'), 'w', encoding='utf-8') as f:f.write('''DATABASE_URL=postgres://localhost:5432/mydbAPI_KEY=your-api-key''')# Create directory and filegithub_dir = os.path.join(directory, '.github', 'workflows')os.makedirs(github_dir, exist_ok=True)with open(os.path.join(github_dir, 'ci.yml'), 'w', encoding='utf-8') as f:f.write('''name: CIon: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- run: npm ci- run: npm test''')return {'directory': directory}
Plugin.cs
[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;// Create .gitignoreawait File.WriteAllTextAsync(Path.Combine(directory, ".gitignore"),@"node_modules/dist/.env*.log");// Create .env.exampleawait File.WriteAllTextAsync(Path.Combine(directory, ".env.example"),@"DATABASE_URL=postgres://localhost:5432/mydbAPI_KEY=your-api-key");// Create directory and filevar githubDir = Path.Combine(directory, ".github", "workflows");Directory.CreateDirectory(githubDir);await File.WriteAllTextAsync(Path.Combine(githubDir, "ci.yml"),@"name: CIon: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- run: npm ci- run: npm test");return new PluginOutput { Directory = directory };}
Delete Files
plugin.ts
StartPluginWithLambda(async (input) => {const { directory } = input;// Delete specific filesconst filesToDelete = ['CONTRIBUTING.md','LICENSE','.github/FUNDING.yml'];for (const file of filesToDelete) {const filePath = path.join(directory, file);try {await fs.unlink(filePath);} catch {// File doesn't exist, ignore}}return { directory };});
plugin.py
@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directory# Delete specific filesfiles_to_delete = ['CONTRIBUTING.md','LICENSE','.github/FUNDING.yml']for file in files_to_delete:file_path = os.path.join(directory, file)try:os.remove(file_path)except FileNotFoundError:# File doesn't exist, ignorepassreturn {'directory': directory}
Plugin.cs
[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;// Delete specific filesvar filesToDelete = new[]{"CONTRIBUTING.md","LICENSE",".github/FUNDING.yml"};foreach (var file in filesToDelete){var filePath = Path.Combine(directory, file);try{File.Delete(filePath);}catch (FileNotFoundException){// File doesn't exist, ignore}}return new PluginOutput { Directory = directory };}
Using Glob Patterns
Process multiple files matching a pattern using the glob npm package:
npm install glob
yarn add glob
pnpm add glob
plugin.ts
import { glob } from 'glob';StartPluginWithLambda(async (input) => {const { directory } = input;// Find all markdown filesconst mdFiles = await glob('**/*.md', { cwd: directory });for (const file of mdFiles) {const filePath = path.join(directory, file);let content = await fs.readFile(filePath, 'utf-8');// Add frontmatter if missingif (!content.startsWith('---')) {content = `---title: ${path.basename(file, '.md')}---${content}`;await fs.writeFile(filePath, content);}}return { directory };});
plugin.py
import glob as glob_module@start_plugin_with_fnasync def main(input: CyanPluginInput) -> PluginOutput:directory = input.directory# Find all markdown filesmd_files = glob_module.glob(os.path.join(directory, '**/*.md'), recursive=True)for file_path in md_files:with open(file_path, 'r', encoding='utf-8') as f:content = f.read()# Add frontmatter if missingif not content.startswith('---'):title = os.path.splitext(os.path.basename(file_path))[0]content = f'''---title: {title}---{content}'''with open(file_path, 'w', encoding='utf-8') as f:f.write(content)return {'directory': directory}
Plugin.cs
using System.Text.RegularExpressions;[PluginMain]public static async Task<PluginOutput> Main(CyanPluginInput input){var directory = input.Directory;// Find all markdown filesvar mdFiles = Directory.GetFiles(directory, "*.md", SearchOption.AllDirectories);foreach (var filePath in mdFiles){var content = await File.ReadAllTextAsync(filePath);// Add frontmatter if missingif (!content.StartsWith("---")){var title = Path.GetFileNameWithoutExtension(filePath);content = $"---\ntitle: {title}\n---\n\n{content}";await File.WriteAllTextAsync(filePath, content);}}return new PluginOutput { Directory = directory };}
All examples assume the imports shown in the first code block. When modifying files, be careful not to break the generated output. Test your plugin with various templates to ensure compatibility.
Related
- Run Shell Commands - Execute commands
- Conditional Execution - Run based on config
- Plugin Input/Output - API reference