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:
This builds and installs 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:
cd ../sitectl-isle && make install
cd ../sitectl-drupal && make install
Application plugin template
Use sitectl-app-tmpl when starting a new application plugin for a Compose-backed app that uses Traefik, MariaDB, and Solr. The template includes the standard SDK entrypoint, Compose create and lifecycle registration, service plugin includes, app database helpers, GitHub Actions workflows, GoReleaser config, Makefile targets, and a local go.work helper.
After creating a repo from the template, replace the app constants, module path, binary name, Compose template repository, project discovery rules, and app-specific commands. Keep local workspace wiring in go.work; do not add sibling-module replace directives to go.mod.
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 of replace directives:
This runs scripts/use-go-work.sh, which creates a go.work file that points the plugin at ../sitectl. The go.work file is gitignored — it’s local only and does not affect CI or releases.
Never add a replace directive to go.mod for local development. Use make work instead. Replace directives affect everyone who builds the module; go.work files are local.
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:
sdk.SetComposeProjectDiscovery(plugin.ComposeProjectDiscovery{
RequiredServices: []string{"drupal"},
RequiredComposerPackages: []string{"drupal/islandora"},
Reason: "drupal service with drupal/islandora in composer.json",
})
The SDK exposes this through the private __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
This runs the same lint and test invocation used in GitHub Actions. Run it before pushing. Lint includes gofmt and golangci-lint. Tests run with -race.
To run a single test:
go test -v -race ./cmd -run TestFcrepoComponent
Integration tests (ISLE plugin)
The ISLE plugin has an end-to-end create test that exercises the full site creation flow:
make integration-test FCREPO_STATE=off ISLE_FILE_SYSTEM_URI=public SITECTL_CONTEXT=isle-test
This is the same script the GitHub Actions integration workflow runs. It requires Docker and a functioning sitectl installation.
Key command flags
sitectl create isle
| Flag | Default | Description |
|---|
--template-repo | https://github.com/islandora-devops/isle-site-template | Template repository to clone |
--template-branch | main | Branch to clone from |
--git-remote-url | — | If set, keeps template as upstream and adds this as origin |
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 defaults it to ./drupal so Drupal-specific paths like composer.json and config/sync resolve correctly for the site template layout.
Use --codebase-rootfs in examples and tests. Compatibility flags may exist in older plugins, but docs should show the canonical rootfs flag.
sitectl component describe --path /path/to/project
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 converge --report
sitectl validate
sitectl verify --fcrepo off --iiif triplet --bot-mitigation on
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.
sitectl --context integration-test create drupal/default --path ./drupal --type local
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