LogoCyanPrint

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 file
const 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, PluginOutput
import os
@start_plugin_with_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
# Read file
readme_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 file
var 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_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
readme_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');
// Read
let content = await fs.readFile(readmePath, 'utf-8');
// Modify
content = content + '\n\n## Generated\n\nThis project was generated by CyanPrint.';
// Write back
await fs.writeFile(readmePath, content);
return { directory };
});
plugin.py
@start_plugin_with_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
readme_path = os.path.join(directory, 'README.md')
# Read
with open(readme_path, 'r', encoding='utf-8') as f:
content = f.read()
# Modify
content = content + '\n\n## Generated\n\nThis project was generated by CyanPrint.'
# Write back
with 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");
// Read
var content = await File.ReadAllTextAsync(readmePath);
// Modify
content += "\n\n## Generated\n\nThis project was generated by CyanPrint.";
// Write back
await File.WriteAllTextAsync(readmePath, content);
return new PluginOutput { Directory = directory };
}

Common Patterns

Update JSON File

plugin.ts
StartPluginWithLambda(async (input) => {
const { directory } = input;
// Read package.json
const pkgPath = path.join(directory, 'package.json');
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
// Modify
pkg.scripts = pkg.scripts || {};
pkg.scripts.dev = 'next dev --turbo';
pkg.scripts.build = 'next build';
// Add dependencies
pkg.dependencies = pkg.dependencies || {};
pkg.dependencies['lodash'] = '^4.17.21';
// Write back
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
return { directory };
});
plugin.py
import json
@start_plugin_with_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
# Read package.json
pkg_path = os.path.join(directory, 'package.json')
with open(pkg_path, 'r', encoding='utf-8') as f:
pkg = json.load(f)
# Modify
pkg.setdefault('scripts', {})
pkg['scripts']['dev'] = 'next dev --turbo'
pkg['scripts']['build'] = 'next build'
# Add dependencies
pkg.setdefault('dependencies', {})
pkg['dependencies']['lodash'] = '^4.17.21'
# Write back
with 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.json
var pkgPath = Path.Combine(directory, "package.json");
var jsonContent = await File.ReadAllTextAsync(pkgPath);
var pkg = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonContent);
// Modify
var scripts = new Dictionary<string, string>
{
["dev"] = "next dev --turbo",
["build"] = "next build"
};
pkg["scripts"] = scripts;
// Add dependencies
var dependencies = new Dictionary<string, string>
{
["lodash"] = "^4.17.21"
};
pkg["dependencies"] = dependencies;
// Write back
var 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 exists
try {
let content = await fs.readFile(readmePath, 'utf-8');
// Add author line if provided
if (cfg.author) {
content = content.replace(
'# ',
`# Project\n\nAuthor: ${cfg.author}\n\n`
);
await fs.writeFile(readmePath, content);
}
} catch {
// File doesn't exist, create it
await fs.writeFile(readmePath, `# Project\n\nAuthor: ${cfg.author || 'Unknown'}\n`);
}
return { directory };
});
plugin.py
@start_plugin_with_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
cfg = input.config or {}
author = cfg.get('author')
readme_path = os.path.join(directory, 'README.md')
# Check if file exists
try:
with open(readme_path, 'r', encoding='utf-8') as f:
content = f.read()
# Add author line if provided
if 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 it
with 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 exists
try
{
var content = await File.ReadAllTextAsync(readmePath);
// Add author line if provided
if (author != null)
{
content = content.Replace(
"# ",
$"# Project\n\nAuthor: {author}\n\n"
);
await File.WriteAllTextAsync(readmePath, content);
}
}
catch (FileNotFoundException)
{
// File doesn't exist, create it
await 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 .gitignore
await fs.writeFile(
path.join(directory, '.gitignore'),
`node_modules/
dist/
.env
*.log
`
);
// Create .env.example
await fs.writeFile(
path.join(directory, '.env.example'),
`DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=your-api-key
`
);
// Create directory and file
const githubDir = path.join(directory, '.github', 'workflows');
await fs.mkdir(githubDir, { recursive: true });
await fs.writeFile(
path.join(githubDir, 'ci.yml'),
`name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
`
);
return { directory };
});
plugin.py
@start_plugin_with_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
# Create .gitignore
with open(os.path.join(directory, '.gitignore'), 'w', encoding='utf-8') as f:
f.write('''node_modules/
dist/
.env
*.log
''')
# Create .env.example
with open(os.path.join(directory, '.env.example'), 'w', encoding='utf-8') as f:
f.write('''DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=your-api-key
''')
# Create directory and file
github_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: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- 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 .gitignore
await File.WriteAllTextAsync(
Path.Combine(directory, ".gitignore"),
@"node_modules/
dist/
.env
*.log
"
);
// Create .env.example
await File.WriteAllTextAsync(
Path.Combine(directory, ".env.example"),
@"DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=your-api-key
"
);
// Create directory and file
var githubDir = Path.Combine(directory, ".github", "workflows");
Directory.CreateDirectory(githubDir);
await File.WriteAllTextAsync(
Path.Combine(githubDir, "ci.yml"),
@"name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- 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 files
const 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_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
# Delete specific files
files_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, ignore
pass
return {'directory': directory}
Plugin.cs
[PluginMain]
public static async Task<PluginOutput> Main(CyanPluginInput input)
{
var directory = input.Directory;
// Delete specific files
var 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 files
const 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 missing
if (!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_fn
async def main(input: CyanPluginInput) -> PluginOutput:
directory = input.directory
# Find all markdown files
md_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 missing
if 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 files
var mdFiles = Directory.GetFiles(directory, "*.md", SearchOption.AllDirectories);
foreach (var filePath in mdFiles)
{
var content = await File.ReadAllTextAsync(filePath);
// Add frontmatter if missing
if (!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.