Quick start
Build your first Codify plugin in minutes
Installation
First, install the plugin SDK in your Node.js project:
npm install @codifycli/plugin-core @codifycli/schemasThe @codifycli/plugin-core package provides the base classes and utilities you'll use to build your plugin, while @codifycli/schemas contains TypeScript types and validation schemas for IPC communication.
Minimal Example
Let's build a simple plugin that manages git global configuration. This example demonstrates all the core concepts you'll need to understand plugin development.
The Problem
Users often need to configure git with their name and email. Traditionally, this requires running:
git config --global user.name "John Doe"
git config --global user.email "john@example.com"With Codify, users should be able to declare this in their codify.jsonc:
{
"type": "git-config",
"userName": "John Doe",
"userEmail": "john@example.com"
}Here's the complete plugin implementation:
import { Resource, ResourceSettings, Plugin, runPlugin, getPty } from '@codifycli/plugin-core';
import { StringIndexedObject } from '@codifycli/schemas';
interface GitConfig extends StringIndexedObject {
userName?: string;
userEmail?: string;
}
class GitConfigResource extends Resource<GitConfig> {
getSettings(): ResourceSettings<GitConfig> {
return {
id: 'git-config',
operatingSystems: ['darwin', 'linux'],
schema: {
type: 'object',
properties: {
userName: { type: 'string' },
userEmail: { type: 'string' }
}
}
};
}
async refresh(parameters: Partial<GitConfig>) {
const pty = getPty();
const nameResult = await pty.spawnSafe('git config --global user.name');
const emailResult = await pty.spawnSafe('git config --global user.email');
return {
userName: nameResult.status === 'success' ? nameResult.data.trim() : undefined,
userEmail: emailResult.status === 'success' ? emailResult.data.trim() : undefined
};
}
async create(plan) {
const pty = getPty();
const config = plan.desiredConfig;
if (config.userName) {
await pty.spawn(`git config --global user.name "${config.userName}"`);
}
if (config.userEmail) {
await pty.spawn(`git config --global user.email "${config.userEmail}"`);
}
}
async destroy(plan) {
const pty = getPty();
await pty.spawn('git config --global --unset user.name');
await pty.spawn('git config --global --unset user.email');
}
}
const plugin = Plugin.create('my-plugin', [new GitConfigResource()]);
runPlugin(plugin);Understanding the Example
Let's break down each part of this plugin:
1. Configuration Interface
interface GitConfig extends StringIndexedObject {
userName?: string;
userEmail?: string;
}This TypeScript interface defines the shape of your resource's configuration. It must extend StringIndexedObject to ensure compatibility with Codify's type system. All properties are optional because users might configure only one property.
2. Resource Settings
getSettings(): ResourceSettings<GitConfig> {
return {
id: 'git-config', // The "type" field users use in configs
operatingSystems: ['darwin', 'linux'], // macOS and Linux only
schema: { /* JSON Schema */ } // Validates user input
};
}The getSettings() method tells Codify everything it needs to know about your resource: its unique identifier, which operating systems it supports, and how to validate user configurations.
3. Refresh (Query Current State)
async refresh(parameters: Partial<GitConfig>) {
const pty = getPty();
const nameResult = await pty.spawnSafe('git config --global user.name');
// ...
return {
userName: nameResult.status === 'success' ? nameResult.data.trim() : undefined,
userEmail: emailResult.status === 'success' ? emailResult.data.trim() : undefined
};
}The refresh() method queries the current system state. It uses spawnSafe() to run git commands without throwing errors if the config doesn't exist. Returning undefined for properties means they're not currently set.
4. Create (Install/Configure)
async create(plan) {
const pty = getPty();
const config = plan.desiredConfig;
if (config.userName) {
await pty.spawn(`git config --global user.name "${config.userName}"`);
}
// ...
}The create() method receives a plan containing the desired configuration and executes the commands to achieve that state. Using spawn() (not spawnSafe()) means errors will be thrown and handled by the framework.
5. Destroy (Remove/Uninstall)
async destroy(plan) {
const pty = getPty();
await pty.spawn('git config --global --unset user.name');
// ...
}The destroy() method removes the resource from the system. In stateful mode, this gets called when a user removes the resource from their config.
6. Plugin Registration
const plugin = Plugin.create('my-plugin', [new GitConfigResource()]);
runPlugin(plugin);Finally, we create a plugin instance with our resource and call runPlugin() to start the IPC message loop. The plugin process will now respond to requests from the Codify CLI.
Using Your Plugin
Users can now use your resource in their codify.jsonc:
{
"type": "git-config",
"userName": "John Doe",
"userEmail": "john@example.com"
}When they run codify apply, Codify will:
- Validate the config against your schema
- Call
refresh()to check current git configuration - Generate a plan showing which properties will be set
- Call
create()to execute the git config commands
Next Steps
Now that you've built your first plugin, learn more about:
- Core Concepts - Deep dive into plugins, resources, and plans
- Resource Lifecycle - Detailed guide to implementing lifecycle methods
- Testing - How to test your plugin thoroughly