PluginsExplanation
Plugins vs Processors
When to use plugins versus processors
Plugins vs Processors
Both plugins and processors extend CyanPrint's capabilities, but they serve different purposes in the generation pipeline.
Quick Comparison
| Aspect | Processor | Plugin |
|---|---|---|
| Role | Transform file content | Execute operations |
| When | During file generation | After generation completes |
| Access | File content via helper | Full directory access |
| State | Pure function | Can have side effects |
| API | CyanFileHelper | Direct fs access |
Processors: File Transformation
Processors transform file content. They operate on individual files during the generation process.
What Processors Do
- Template variable substitution
- Syntax transformation
- Code generation
- File content processing
Processor Example
index.ts
import { StartProcessorWithLambda } from '@atomicloud/cyan-sdk';StartProcessorWithLambda(async (input, fileHelper) => {const files = fileHelper.resolveAll();files.forEach(file => {// Transform file contentfile.content = file.content.replaceAll('var__name__', 'MyProject');file.writeFile();});return { directory: input.writeDir };});
main.py
from cyanprintsdk.processor import start_processor_with_fnfrom cyanprintsdk.protocol import ProcessorInput, ProcessorOutputasync def processor(input: ProcessorInput, file_helper) -> ProcessorOutput:files = file_helper.resolve_all()for file in files:# Transform file contentfile.content = file.content.replace('var__name__', 'MyProject')file.write_file()return ProcessorOutput(directory=input.write_dir)processor_main = start_processor_with_fn(processor)
Processor.cs
using CyanPrintSDK;public static class Processor{[ProcessorMain]public static async Task<ProcessorOutput> Run(ProcessorInput input, CyanFileHelper fileHelper){var files = fileHelper.ResolveAll();foreach (var file in files){// Transform file contentfile.Content = file.Content.Replace("var__name__", "MyProject");file.WriteFile();}return new ProcessorOutput { Directory = input.WriteDir };}}
Key Points
- Receives
CyanFileHelperfor file operations - Transforms content before files are written
- Must be pure (same input → same output)
- Cannot execute shell commands
Plugins: Post-Processing Operations
Plugins run operations after all files are generated. They have full access to the filesystem and can execute commands.
What Plugins Do
- Run shell commands (git, npm, etc.)
- Install dependencies
- Initialize version control
- Set up development tooling
Plugin Example
index.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';import fs from 'node:fs';import path from 'node:path';StartPluginWithLambda(async (input) => {const { directory } = input;// Create files or modify the generated outputconst gitDir = path.join(directory, '.git');if (!fs.existsSync(gitDir)) {fs.mkdirSync(gitDir, { recursive: true });// Initialize git structure...}// Write configuration filesconst configPath = path.join(directory, 'config.json');fs.writeFileSync(configPath, JSON.stringify({ initialized: true }));return { directory };});
main.py
import osimport jsonfrom cyanprintsdk.plugin import start_plugin_with_fnfrom cyanprintsdk.protocol import PluginInput, PluginOutputasync def plugin(input: PluginInput) -> PluginOutput:directory = input.directory# Create files or modify the generated outputgit_dir = os.path.join(directory, '.git')if not os.path.exists(git_dir):os.makedirs(git_dir, exist_ok=True)# Initialize git structure...# Write configuration filesconfig_path = os.path.join(directory, 'config.json')with open(config_path, 'w') as f:json.dump({'initialized': True}, f)return PluginOutput(directory=directory)plugin_main = start_plugin_with_fn(plugin)
Plugin.cs
using CyanPrintSDK;using System.IO;using System.Text.Json;public static class Plugin{[PluginMain]public static async Task<PluginOutput> Run(PluginInput input){var directory = input.Directory;// Create files or modify the generated outputvar gitDir = Path.Combine(directory, ".git");if (!Directory.Exists(gitDir)){Directory.CreateDirectory(gitDir);// Initialize git structure...}// Write configuration filesvar configPath = Path.Combine(directory, "config.json");var config = new { initialized = true };await File.WriteAllTextAsync(configPath, JsonSerializer.Serialize(config));return new PluginOutput { Directory = directory };}}
Key Points
- No file helper - direct filesystem access
- Runs after all processors complete
- Can execute shell commands (via child_process or bun shell)
- Can have side effects (installing packages, etc.)
Execution Order
- Template collects user input
- Processors run in sequence (transform content)
- All files are written to disk
- Plugins run in sequence (post-processing)
- Final output delivered to user
When to Use Which
Use a Processor When:
| Scenario | Example |
|---|---|
| Substitute variables | var__name__ → MyProject |
| Transform file syntax | Convert Eta to final output |
| Generate code | Create files from templates |
| Process file content | Add headers, format code |
| Filter files | Include/exclude based on config |
Use a Plugin When:
| Scenario | Example |
|---|---|
| Run git commands | git init, git commit |
| Install dependencies | npm install, bun install |
| Run formatters | prettier --write . |
| Set up tooling | husky install |
| Build steps | npm run build |
| Create symlinks | Link config files |
Using Both Together
Many templates need both processors and plugins:
index.ts
// In template's index.tsreturn {processors: [{name: 'cyan/default', // Transform templatesfiles: [{ root: 'templates', glob: '**/*', type: GlobType.Template }],config: { vars: { name, version } }}],plugins: [{name: 'atomi/setup-plugin', // Post-processingconfig: {git: true,installDeps: true}}]};
main.py
# In template's main.pyreturn Cyan(processors=[CyanProcessor(name='cyan/default', # Transform templatesfiles=[CyanGlob(root='templates', glob='**/*', type=GlobType.TEMPLATE)],config={'vars': {'name': name, 'version': version}})],plugins=[CyanPlugin(name='atomi/setup-plugin', # Post-processingconfig={'git': True,'installDeps': True})])
Template.cs
// In Template.csreturn new Cyan{Processors = new[]{new CyanProcessor{Name = "cyan/default", // Transform templatesFiles = new List<CyanGlob>{new CyanGlob { Root = "templates", Glob = "**/*", Type = GlobType.Template }},Config = new Dictionary<string, object>{["vars"] = new Dictionary<string, string> { ["name"] = name, ["version"] = version }}}},Plugins = new[]{new CyanPlugin{Name = "atomi/setup-plugin", // Post-processingConfig = new Dictionary<string, object>{["git"] = true,["installDeps"] = true}}}};
Typical Workflow
- Processor substitutes
var__name__with actual project name - Processor transforms template files
- Files are written to output directory
- Plugin initializes git repository
- Plugin installs dependencies
- User receives complete, ready-to-use project
Use processors for content transformation and plugins for operations. Together they provide a complete generation pipeline.