Local install
For local development, install all binaries into a directory on your$PATH so core sitectl can discover and invoke the plugin binaries. From the core repo:
sitectl, then changes into ../sitectl-isle and ../sitectl-drupal and runs make install in each. The plugin install targets run make work before building so they use the local sitectl checkout.
If you’re only working on a single plugin:
Application plugin template
Usesitectl-app-tmpl v1.0.0 when starting a new application plugin for a Compose-backed app that uses shared services such as Traefik, MariaDB, or Solr. The template includes the standard SDK entrypoint, Compose create and rollout metadata, project discovery, ingress wiring, an explicit dev-mode opt-out, an app exec helper, debug/validate/healthcheck runners, a warning-only verification scaffold, GitHub Actions workflows, GoReleaser config, Makefile targets, and a local go.work helper. Pin the scaffold release and exact tag commit used to create the repository; do not generate a release-bound plugin from a moving main checkout.
After creating a repo from the template, replace the app constants, module path, binary name, Compose template repository, project discovery rules, migration placeholder, warning-only verification runner, and app-specific commands. Core sitectl compose owns build, init, up, down, logs, status, and rollout behavior; core service commands own shared MariaDB, Solr, Traefik, Valkey, and Memcached operations. Keep local workspace wiring in go.work; do not add sibling-module replace directives to go.mod.
See Application plugin template for the template checklist.
Developing against a local sitectl checkout
Plugins import sitectl as a Go module dependency. When you need to test plugin changes against an unreleased version of sitectl, use Go workspaces instead ofreplace directives:
scripts/use-go-work.sh, which creates a go.work file that points the plugin at ../sitectl. The go.work file is gitignored. It is local only and does not affect CI or releases.
Current working directory context detection
sitectl can use the current working directory as the active context when it is inside a supported Compose project. Core sitectl finds the Compose project root, then asks installed plugins whether they claim that project. The plugin owns the discovery rules. Plugins should register discovery during startup:__sitectl-rpc entrypoint using the project.detect method. Core sitectl uses that method during lightweight plugin discovery and creates a transient local context named . when the project has no matching saved context. This context is in memory only; discovery must not write to ~/.sitectl/config.yaml.
Discovery is cached for the lifetime of the sitectl process by canonical project path and requested plugin. Repeated subcommands in one invocation should not repeatedly shell out to every plugin.
Plugin-specific commands must use the shared SDK context resolver (sdk.GetContext) so --context . works consistently. Commands that bypass the SDK need an explicit reason and tests for cwd detection.
Linting and testing
gofmt and golangci-lint. Tests run with -race.
To run a single test:
Integration tests (ISLE plugin)
The ISLE plugin has an end-to-endcreate test that exercises the full site creation flow:
Key command flags
sitectl create isle
After create, configure git remotes with normal git commands. sitectl removes the template repository’s
.git history before initializing the new checkout, so rollout only pulls when the active branch has an upstream configured outside sitectl.
sitectl component describe
sitectl component describe and sitectl component reconcile accept --codebase-rootfs. The shared RPC contract carries this as codebase_rootfs so the same field works for Drupal and non-Drupal plugins. ISLE first uses the active context’s saved rootfs. Without one, it detects the supported legacy nested layout, drupal, or the repository root; a v1 git-root create stores .. Pass --codebase-rootfs only when an explicit override is needed.
Use --codebase-rootfs in examples and tests. Compatibility flags may exist in older plugins, but docs should show the canonical rootfs flag.
sitectl converge / sitectl validate / sitectl verify
These commands use DisableFlagParsing: true and forward unclaimed flags to the plugin’s registered runner. The ISLE plugin’s ConvergeRunner and ValidateRunner both accept --codebase-rootfs to resolve the Drupal web root correctly. The ISLE plugin’s VerifyRunner accepts expected-state flags such as --fcrepo, --blazegraph, --iiif, --iiif-topology, and --bot-mitigation.
sitectl create
create also uses DisableFlagParsing: true and forwards every argument after it to
the selected plugin’s create runner. As a result, global sitectl flags such as
--context and --log-level must be placed before the create subcommand, while
plugin create flags (--path, --type, --checkout-source, --setup-only, and so
on) go after the target. Placing --context after create forwards it to the plugin,
which fails with unknown flag: --context.
Why make install matters for plugin chaining
Rebuilding sitectl locally without reinstalling the plugin binaries means core command dispatch will not see your current plugin builds. The full install chain keeps all three binaries aligned while you work across:
- core command routing in
sitectl - stack logic in
sitectl-isle - Drupal-specific extensions in
sitectl-drupal
Core and Plugin Release Order
Plugin executables embed the sitectl SDK version selected by theirgo.mod; installing a newer core binary does not update SDK behavior inside an already-built plugin. The reviewed v1 plugin set uses core v1.0.0 and RPC schema 1. Treat a major core change as a compatibility boundary even when a package dependency written as a lower bound would accept it.
Use this order for any release that raises the minimum core SDK version:
- Tag and publish core
sitectl. Wait for its complete release workflow, native package publication, and Homebrew formula update to pass before releasing a plugin that requires it. - In each affected plugin, update
github.com/libops/sitectlto the published stable version, rungo mod tidy, ensure integration tests install that core version or newer, and run the full plugin tests without a localgo.workoverride. - Release
sitectl-drupaland wait for all publication jobs before releasingsitectl-isle; then release the other application plugins. ISLE v1 packages requiresitectl-drupal >= 1.0.0as well assitectl >= 1.0.0, so the publish and install dependency order is core, Drupal, then ISLE. Homebrew cannot express the same minimum versions reliably, making this ordering the compatibility gate there. - Update and release
sitectl-app-tmplafter the core SDK it demonstrates is published. Its warning-only verification and migration placeholders are scaffold instructions, not finished behavior for a derived application plugin. - Update
sitectl-docslast. Record each published v1-compatible version and exact tag commit inscripts/snippet-dependencies.json, regenerate the snippets from those commits, and merge only after the manifest checker proves every tag resolves to its recorded SHA. Keep an excluded incompatible plugin out of the active module and generated command reference until it has a compatible stable release.
scripts/check-sitectl-core-version.sh and refuse a release built with an older or prerelease SDK. The v1 Debian, RPM, and APK packages declare their format-specific sitectl >= 1.0.0 dependency, but release planning must still respect major protocol boundaries. Renovate can propose later module upgrades after tags exist, but the initial core-first bump is an intentional release step.
