LogoCyanPrint

Conditional Execution

Run plugin logic based on configuration

Conditional Execution

Run plugin logic based on configuration from the template. This allows templates to control which post-processing steps run.

Using Config

The config property passed to plugins contains configuration defined in the template:

plugin.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
import { $ } from 'bun';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
// Define your config type
const cfg = config as {
git?: boolean;
installDeps?: boolean;
packageManager?: 'npm' | 'bun' | 'yarn' | 'pnpm';
formatter?: 'prettier' | 'biome' | 'none';
runBuild?: boolean;
};
// Git initialization
if (cfg.git) {
await $`git -C ${directory} init`.quiet();
await $`git -C ${directory} add .`.quiet();
await $`git -C ${directory} commit -m "Initial commit"`.quiet();
}
// Install dependencies
if (cfg.installDeps) {
const pm = cfg.packageManager || 'npm';
await $`cd ${directory} && ${pm} install`.quiet();
}
// Run formatter
if (cfg.formatter === 'prettier') {
await $`cd ${directory} && npx prettier --write .`.quiet();
} else if (cfg.formatter === 'biome') {
await $`cd ${directory} && npx biome format --write .`.quiet();
}
// Run build
if (cfg.runBuild) {
await $`cd ${directory} && npm run build --if-present`.quiet();
}
return { directory };
});
plugin.py
from cyan_sdk import start_plugin_with_fn
import subprocess
import os
def run_command(cmd, cwd=None):
subprocess.run(cmd, shell=True, cwd=cwd, check=True, capture_output=True)
@start_plugin_with_fn
def main(input_data):
directory = input_data["directory"]
config = input_data.get("config", {})
# Git initialization
if config.get("git"):
run_command("git init", cwd=directory)
run_command("git add .", cwd=directory)
run_command('git commit -m "Initial commit"', cwd=directory)
# Install dependencies
if config.get("install_deps"):
pm = config.get("package_manager", "npm")
run_command(f"{pm} install", cwd=directory)
# Run formatter
formatter = config.get("formatter")
if formatter == "prettier":
run_command("npx prettier --write .", cwd=directory)
elif formatter == "biome":
run_command("npx biome format --write .", cwd=directory)
# Run build
if config.get("run_build"):
run_command("npm run build --if-present", cwd=directory)
return {"directory": directory}
Plugin.cs
using CyanSdk;
using System.Diagnostics;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var cfg = input.Config;
// Git initialization
if (cfg.TryGetValue("git", out var git) && (bool)git)
{
RunCommand($"git -C {directory} init");
RunCommand($"git -C {directory} add .");
RunCommand($"git -C {directory} commit -m \"Initial commit\"");
}
// Install dependencies
if (cfg.TryGetValue("installDeps", out var installDeps) && (bool)installDeps)
{
var pm = cfg.TryGetValue("packageManager", out var pmValue) ? pmValue.ToString() : "npm";
RunCommand($"cd {directory} && {pm} install");
}
// Run formatter
if (cfg.TryGetValue("formatter", out var formatter))
{
if (formatter.ToString() == "prettier")
RunCommand($"cd {directory} && npx prettier --write .");
else if (formatter.ToString() == "biome")
RunCommand($"cd {directory} && npx biome format --write .");
}
// Run build
if (cfg.TryGetValue("runBuild", out var runBuild) && (bool)runBuild)
{
RunCommand($"cd {directory} && npm run build --if-present");
}
return new PluginOutput { Directory = directory };
}
private static void RunCommand(string command)
{
var parts = command.Split(' ');
var process = Process.Start(new ProcessStartInfo
{
FileName = parts[0],
Arguments = string.Join(" ", parts.Skip(1)),
RedirectStandardOutput = true,
RedirectStandardError = true
});
process.WaitForExit();
}
}

Template Configuration

Configure the plugin in your template's index.ts file. The plugin configuration is defined programmatically in the return statement of your template's main function:

template/index.ts
import type { Cyan } from '@atomicloud/cyan-sdk';
import type { IInquirer, IDeterminism } from '@atomicloud/cyan-sdk';
// In your template's index.ts, obtain answers from inquirer prompts
export default async function (i: IInquirer, d: IDeterminism): Promise<Cyan> {
// ... prompt users for answers using inquirer ...
return {
processors: [
// ... processors
],
plugins: [{
name: 'myorg/setup-plugin',
config: {
git: true,
installDeps: true, // or use answers.installDeps from prompts
packageManager: 'bun', // or use answers.packageManager from prompts
formatter: 'biome',
runBuild: false
}
}]
};
}
template/index.py
from cyan_sdk import Cyan, PluginConfig
# In your template's index.py
def main(inquirer, determinism):
# ... prompt users for answers using inquirer ...
return Cyan(
processors=[
# ... processors
],
plugins=[PluginConfig(
name='myorg/setup-plugin',
config={
'git': True,
'install_deps': True, # or use answers['install_deps']
'package_manager': 'bun', # or use answers['package_manager']
'formatter': 'biome',
'run_build': False
}
)]
)
Template/Index.cs
using CyanSdk;
public static class Template
{
public static async Task<Cyan> Main(IInquirer inquirer, IDeterminism determinism)
{
// ... prompt users for answers using inquirer ...
return new Cyan
{
Processors = new List<Processor>
{
// ... processors
},
Plugins = new List<PluginConfig>
{
new PluginConfig
{
Name = "myorg/setup-plugin",
Config = new Dictionary<string, object>
{
["git"] = true,
["installDeps"] = true, // or use answers.InstallDeps
["packageManager"] = "bun", // or use answers.PackageManager
["formatter"] = "biome",
["runBuild"] = false
}
}
}
};
}
}

Advanced Patterns

The code examples below use Bun's shell template syntax (await $command``) which requires the Bun runtime. Import it with import { $ } from 'bun';. When using fs and path operations, import them with import fs from 'node:fs'; and import path from 'node:path';.

Feature Flags

plugin.ts
import fs from 'node:fs';
import path from 'node:path';
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
import { $ } from 'bun';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
const features = config as {
features?: {
eslint?: boolean;
prettier?: boolean;
testing?: boolean;
ci?: boolean;
};
};
const enabledFeatures = features.features || {};
if (enabledFeatures.eslint) {
await $`cd ${directory} && npm install -D eslint`.quiet();
// Create .eslintrc.json
await fs.writeFile(
path.join(directory, '.eslintrc.json'),
JSON.stringify({ extends: ['eslint:recommended'] }, null, 2)
);
}
if (enabledFeatures.prettier) {
await $`cd ${directory} && npm install -D prettier`.quiet();
}
if (enabledFeatures.testing) {
await $`cd ${directory} && npm install -D vitest`.quiet();
}
if (enabledFeatures.ci) {
// Create GitHub Actions workflow
const workflowsDir = path.join(directory, '.github', 'workflows');
await fs.mkdir(workflowsDir, { recursive: true });
await fs.writeFile(
path.join(workflowsDir, 'ci.yml'),
`name: CI\non: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci\n - run: npm test\n`
);
}
return { directory };
});
plugin.py
from cyan_sdk import start_plugin_with_fn
import subprocess
import os
import json
def run_command(cmd, cwd=None):
subprocess.run(cmd, shell=True, cwd=cwd, check=True, capture_output=True)
@start_plugin_with_fn
def main(input_data):
directory = input_data["directory"]
config = input_data.get("config", {})
features = config.get("features", {})
if features.get("eslint"):
run_command("cd {} && npm install -D eslint".format(directory))
# Create .eslintrc.json
with open(os.path.join(directory, ".eslintrc.json"), "w") as f:
json.dump({"extends": ["eslint:recommended"]}, f, indent=2)
if features.get("prettier"):
run_command("cd {} && npm install -D prettier".format(directory))
if features.get("testing"):
run_command("cd {} && npm install -D vitest".format(directory))
if features.get("ci"):
# Create GitHub Actions workflow
workflows_dir = os.path.join(directory, ".github", "workflows")
os.makedirs(workflows_dir, exist_ok=True)
with open(os.path.join(workflows_dir, "ci.yml"), "w") as f:
f.write("name: CI\non: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci\n - run: npm test\n")
return {"directory": directory}
Plugin.cs
using CyanSdk;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var features = input.Config.TryGetValue("features", out var f)
? f as Dictionary<string, object>
: new Dictionary<string, object>();
if (features.TryGetValue("eslint", out var eslint) && (bool)eslint)
{
RunCommand($"cd {directory} && npm install -D eslint");
var eslintConfig = new { extends = new[] { "eslint:recommended" } };
File.WriteAllText(
Path.Combine(directory, ".eslintrc.json"),
JsonSerializer.Serialize(eslintConfig, new JsonSerializerOptions { WriteIndented = true })
);
}
if (features.TryGetValue("prettier", out var prettier) && (bool)prettier)
{
RunCommand($"cd {directory} && npm install -D prettier");
}
if (features.TryGetValue("testing", out var testing) && (bool)testing)
{
RunCommand($"cd {directory} && npm install -D vitest");
}
if (features.TryGetValue("ci", out var ci) && (bool)ci)
{
var workflowsDir = Path.Combine(directory, ".github", "workflows");
Directory.CreateDirectory(workflowsDir);
File.WriteAllText(
Path.Combine(workflowsDir, "ci.yml"),
"name: CI\non: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci\n - run: npm test\n"
);
}
return new PluginOutput { Directory = directory };
}
private static void RunCommand(string command)
{
var parts = command.Split(' ');
var process = Process.Start(new ProcessStartInfo
{
FileName = parts[0],
Arguments = string.Join(" ", parts.Skip(1)),
RedirectStandardOutput = true,
RedirectStandardError = true
});
process.WaitForExit();
}
}

Conditional Based on Project Type

plugin.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
import { $ } from 'bun';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
const cfg = config as {
projectType?: 'nextjs' | 'react' | 'node' | 'cli';
};
switch (cfg.projectType) {
case 'nextjs':
await $`cd ${directory} && npm install next react react-dom`.quiet();
break;
case 'react':
await $`cd ${directory} && npm install react react-dom`.quiet();
break;
case 'node':
// Minimal setup for node
break;
case 'cli':
await $`cd ${directory} && npm install commander`.quiet();
break;
}
return { directory };
});
plugin.py
from cyan_sdk import start_plugin_with_fn
import subprocess
def run_command(cmd, cwd=None):
subprocess.run(cmd, shell=True, cwd=cwd, check=True, capture_output=True)
@start_plugin_with_fn
def main(input_data):
directory = input_data["directory"]
config = input_data.get("config", {})
project_type = config.get("project_type")
if project_type == "nextjs":
run_command("cd {} && npm install next react react-dom".format(directory))
elif project_type == "react":
run_command("cd {} && npm install react react-dom".format(directory))
elif project_type == "node":
# Minimal setup for node
pass
elif project_type == "cli":
run_command("cd {} && npm install commander".format(directory))
return {"directory": directory}
Plugin.cs
using CyanSdk;
using System.Diagnostics;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var projectType = input.Config.TryGetValue("projectType", out var pt)
? pt.ToString()
: null;
switch (projectType)
{
case "nextjs":
RunCommand($"cd {directory} && npm install next react react-dom");
break;
case "react":
RunCommand($"cd {directory} && npm install react react-dom");
break;
case "node":
// Minimal setup for node
break;
case "cli":
RunCommand($"cd {directory} && npm install commander");
break;
}
return new PluginOutput { Directory = directory };
}
private static void RunCommand(string command)
{
var parts = command.Split(' ');
var process = Process.Start(new ProcessStartInfo
{
FileName = parts[0],
Arguments = string.Join(" ", parts.Skip(1)),
RedirectStandardOutput = true,
RedirectStandardError = true
});
process.WaitForExit();
}
}

Using Defaults

plugin.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
import { $ } from 'bun';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
// Provide defaults for all config options
const cfg = {
git: true,
installDeps: true,
packageManager: 'npm' as const,
formatter: 'none' as const,
...(config as Record<string, unknown>)
};
if (cfg.git) {
await $`git -C ${directory} init`.quiet();
}
return { directory };
});
plugin.py
from cyan_sdk import start_plugin_with_fn
import subprocess
def run_command(cmd, cwd=None):
subprocess.run(cmd, shell=True, cwd=cwd, check=True, capture_output=True)
@start_plugin_with_fn
def main(input_data):
directory = input_data["directory"]
config = input_data.get("config", {})
# Provide defaults for all config options
defaults = {
"git": True,
"install_deps": True,
"package_manager": "npm",
"formatter": "none",
}
cfg = {**defaults, **config}
if cfg["git"]:
run_command("git -C {} init".format(directory))
return {"directory": directory}
Plugin.cs
using CyanSdk;
using System.Diagnostics;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var config = input.Config;
// Provide defaults for all config options
var git = config.TryGetValue("git", out var g) ? (bool)g : true;
var installDeps = config.TryGetValue("installDeps", out var i) ? (bool)i : true;
var packageManager = config.TryGetValue("packageManager", out var pm) ? pm.ToString() : "npm";
var formatter = config.TryGetValue("formatter", out var f) ? f.ToString() : "none";
if (git)
{
RunCommand($"git -C {directory} init");
}
return new PluginOutput { Directory = directory };
}
private static void RunCommand(string command)
{
var parts = command.Split(' ');
var process = Process.Start(new ProcessStartInfo
{
FileName = parts[0],
Arguments = string.Join(" ", parts.Skip(1)),
RedirectStandardOutput = true,
RedirectStandardError = true
});
process.WaitForExit();
}
}

Always provide sensible defaults for optional configuration. Templates should work without specifying every option.