PluginsExplanation
What Are Plugins?
Understanding plugins and their role in CyanPrint
What Are Plugins?
Plugins are post-processing components that run after all file generation is complete. They enable actions that processors cannot perform, such as running shell commands and executing system operations.
The Big Picture
Plugins are the final step in the generation pipeline. After processors have transformed and written all files, plugins can:
- Run commands on the generated files
- Modify the filesystem
- Execute build steps
- Initialize version control
What Plugins Can Do
| Capability | Example | Why It's a Plugin |
|---|---|---|
| Initialize git | git init | Requires shell access |
| Install dependencies | npm install | Requires package manager |
| Run formatters | prettier --write . | Requires npm packages |
| Create symlinks | ln -s | Requires filesystem access |
| Build steps | npm run build | Requires toolchain |
| Set up hooks | husky install | Requires git + npm |
Plugins are the only component with shell access. Processors are isolated and cannot execute commands.
How Plugins Work
Input
Plugins receive a CyanPluginInput object:
Prop
Type
Execution
Output
Plugins return a PluginOutput object:
Prop
Type
When to Use Plugins
Use a Plugin When You Need To:
- Initialize a git repository
- Install npm/bun dependencies
- Run code formatters or linters
- Execute build commands
- Create symlinks or special files
- Set up development tooling
Don't Use a Plugin When You Need To:
- Transform file content (use a processor)
- Substitute variables (use a processor)
- Process individual files (use a processor)
Plugin vs Processor
| Aspect | Processor | Plugin |
|---|---|---|
| Purpose | Transform file content | Run operations |
| Access | File content only | Full filesystem + shell |
| When | During file generation | After all files written |
| State | Stateless | Can have side effects |
| Example | Replace var__name__ | Run git init |
Read more in Plugins vs Processors.
Common Plugin Patterns
Setup Plugin
Initialize the project environment:
plugin.ts
import { $ } from 'bun';import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';import type { CyanPluginInput, PluginOutput } from '@atomicloud/cyan-sdk';StartPluginWithLambda(async (input: CyanPluginInput): Promise<PluginOutput> => {const { directory } = input;await $`git -C ${directory} init`.quiet();Bun's shell template literal for running commandsawait $`cd ${directory} && npm install`.quiet();return { directory };});
plugin.py
import subprocessfrom cyan_sdk import start_plugin_with_fnfrom cyan_sdk.types import CyanPluginInput, PluginOutput@start_plugin_with_fnasync def setup_plugin(input: CyanPluginInput) -> PluginOutput:# Extract directory from inputdirectory = input.directory# Run shell commandssubprocess.run(["git", "-C", directory, "init"], capture_output=True)subprocess.run(["npm", "install"], cwd=directory, capture_output=True)# Always return the directory for pipeline continuityreturn PluginOutput(directory=directory)
Plugin.cs
using CyanSdk;using CyanSdk.Types;[PluginMain]public static class SetupPlugin{public static async Task<PluginOutput> Run(CyanPluginInput input){// Extract directory from inputvar directory = input.Directory;// Run shell commandsawait Shell.Exec($"git -C {directory} init");await Shell.Exec($"cd {directory} && npm install");// Always return the directory for pipeline continuityreturn new PluginOutput(directory);}}
Formatter Plugin
Run code formatters:
import { $ } from 'bun';import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';import type { CyanPluginInput, PluginOutput } from '@atomicloud/cyan-sdk';StartPluginWithLambda(async (input: CyanPluginInput): Promise<PluginOutput> => {const { directory, config } = input;const cfg = config as { formatter?: string };if (cfg.formatter === 'prettier') {await $`cd ${directory} && npx prettier --write .`.quiet();}return { directory };});
import subprocessfrom cyan_sdk import start_plugin_with_fnfrom cyan_sdk.types import CyanPluginInput, PluginOutputfrom typing import TypedDictclass PluginConfig(TypedDict, total=False):formatter: str@start_plugin_with_fnasync def formatter_plugin(input: CyanPluginInput) -> PluginOutput:directory = input.directoryconfig: PluginConfig = input.configif config.get("formatter") == "prettier":subprocess.run(["npx", "prettier", "--write", "."],cwd=directory,capture_output=True)return PluginOutput(directory=directory)
using CyanSdk;using CyanSdk.Types;[PluginMain]public static class FormatterPlugin{public static async Task<PluginOutput> Run(CyanPluginInput input){var directory = input.Directory;var config = input.Config as dynamic;if (config?.formatter == "prettier"){await Shell.Exec($"cd {directory} && npx prettier --write .");}return new PluginOutput(directory);}}
Build Plugin
Run build steps:
import { $ } from 'bun';import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';import type { CyanPluginInput, PluginOutput } from '@atomicloud/cyan-sdk';StartPluginWithLambda(async (input: CyanPluginInput): Promise<PluginOutput> => {const { directory, config } = input;const cfg = config as { build?: boolean };if (cfg.build) {await $`cd ${directory} && npm run build`.quiet();}return { directory };});
import subprocessfrom cyan_sdk import start_plugin_with_fnfrom cyan_sdk.types import CyanPluginInput, PluginOutputfrom typing import TypedDictclass BuildConfig(TypedDict, total=False):build: bool@start_plugin_with_fnasync def build_plugin(input: CyanPluginInput) -> PluginOutput:directory = input.directoryconfig: BuildConfig = input.configif config.get("build", False):subprocess.run(["npm", "run", "build"],cwd=directory,capture_output=True)return PluginOutput(directory=directory)
using CyanSdk;using CyanSdk.Types;[PluginMain]public static class BuildPlugin{public static async Task<PluginOutput> Run(CyanPluginInput input){var directory = input.Directory;var config = input.Config as dynamic;if (config?.build == true){await Shell.Exec($"cd {directory} && npm run build");}return new PluginOutput(directory);}}
Design Philosophy
Plugins are designed to be:
- Simple - Minimal API, just receive directory and return it
- Flexible - Full filesystem and shell access
- Optional - Templates work without plugins
- Composable - Multiple plugins can be chained
Related
- Plugins vs Processors - Detailed comparison
- Execution Order - Pipeline sequence
- First Plugin - Tutorial