CKli is a tool for multi-repositories stacks. It allows to automate actions (build, package upgrade, etc...), on Worlds (a group of repositories), and concentrates information in a single place.
CKli is implemented in .Net but is not limited to .Net solutions and projects.
The core commands can handle any git repositories regardless of their content. Only the Standard Plugins (see below) are dedicated to .Net development.
CKli is a dotnet tool. You should install it globally by running:
dotnet tool install CKli -gAnd auto-update it with:
ckli updateThe update command handles switching from production and pre-releases easily (described below).
Stable versions are on nuget.org; the CI builds are on the
Signature-OpenSource feed. To manually install a specific version (replace <version> with the one you want):
dotnet tool update CKli@<version> -g --prerelease --add-source https://pkgs.dev.azure.com/Signature-OpenSource/Feeds/_packaging/NetCore3/nuget/v3/index.json --no-http-cacheIf you installed CKli globally, you can run ckli in any command prompt to start it.
ckli --help lists the commands and namespaces; ckli <command> --help details one of them
(--help must be the last argument). The commands below is the whole map, with
where each one is documented.
A few options are global: they belong to ckli itself rather than to a command, and
ckli --help --global details them.
--path <dir>(or-p) sets the working path instead of the current directory. It must come first (before the command): after the command,--pathis an option of that command.--version(or-v) displays the installed CKli version. It must come first and excludes anything else.--ckli-screen <none|no-color|force-ansi>changes the display. A non emptyNO_COLORenvironment variable is honored (see https://no-color.org/).--ckli-debuglaunches a debugger when starting.
ckli i (or ckli interactive) starts an interactive loop where commands are typed one after the other
without the ckli prefix. It accepts --path before or right after it. The commands that create or
relocate something are rejected in interactive mode: clone, create, world lts clone, world lts create,
remote stack migrate and update.
A World is a set of Git repositories. The set is described by a simple XML file that lists the repositories, organizes them in a folder structure and provides plugin configurations. Below is the CKli default world definition file (there is no folder structure here):
<CKli MinCKliVersion="0.14.0">
<Plugins CompileMode="Debug">
<ArtifactHandler>
<NuGet>
<Feed Name="NuGet"
Url="https://api.nuget.org/v3/index.json"
PushQualityFilter="[papa,]">
<Credentials SecretKey="NUGET_ORG_PUSH_API_KEY" />
</Feed>
<Feed Name="Signature-OpenSource"
Url="https://pkgs.dev.azure.com/Signature-OpenSource/Feeds/_packaging/NetCore3/nuget/v3/index.json"
PushQualityFilter="[,],AllowCI">
<Credentials SecretKey="AZURE_FEED_SIGNATURE_OPENSOURCE_PAT" />
</Feed>
</NuGet>
</ArtifactHandler>
<BranchModel />
<Build />
<CommonFiles />
<HotZone />
<Migration />
<Publish />
<ShallowSolution />
<VersionTag />
</Plugins>
<Repository Url="https://github.com/CK-Build/CKli" />
<Repository Url="https://github.com/CK-Build/CK-SVersion" />
<Repository Url="https://github.com/CK-Build/CK-Packaging-Abstractions" />
</CKli>This definition file is stored in the main branch of a Stack repository — a Stack repository has a single
branch and it must be main (a repository that hasn't got it is refused, not adapted to):
ckli clone https://github.com/CK-Build/CKli-Stack
A Stack contain at least one World: the default World that is the current version of the Stack. Long Time Support (LTS) Worlds can be created any time from the default World.
The <Plugins /> element above enables the 9 Standard Plugins. They are optional and each one is
documented on its own:
| Plugin | What it does |
|---|---|
ArtifactHandler |
The NuGet feeds a World reads and pushes to, and the nuget.config of every Repo. |
BranchModel |
The branch structure (stable, the prerelease chain, dev/, explo/) and the branch commands. |
Build |
The build, publish, fix and deps update commands: the roadmap and what it rebuilds. |
CommonFiles |
The files that must be identical (or reconciled) across the Repos. |
HotZone |
The dependency graph of the World and the version zone each Repo is in. |
Migration |
One-off repository conversions (Net8 → Net10). Transient by design. |
Publish |
Pushing the artifacts, the releases, and the Published/ profiles a World leaves behind. |
ShallowSolution |
Reading a solution and its package references straight from a branch, without MSBuild. |
VersionTag |
The version tags (vX.Y.Z, local/, building/, +fake, +deprecated) and their invariants. |
Two workflows span several of them: the branch model and the hot zone and the fix workflow.
A Stack is public or private as a whole: there is no per-repository setting and every Repo of a World
inherits the flag of its Stack. The Stack folder name is where it is recorded — .PublicStack/ or
.PrivateStack/ — and it is chosen once, by the --private flag of
clone
or create.
What the flag actually changes is what needs a secret:
| Read (clone, fetch, pull) | Write (push, create a remote repository, publish a release) | |
|---|---|---|
| Public Stack | no secret at all | a secret is required |
| Private Stack | a secret is required | a secret is required |
So a write secret is always required, even for a public Stack: only anonymous reading is free.
A Stack can reference another Stack that has its own flag: see <Reference Private="true" /> in the
clone
and world reference set
sections. A public Stack cannot reference a private one.
A World definition file only ever holds the name of a key, never a secret value — and you never have to
work that name out yourself. It is computed from the remote url, and whenever an operation needs a secret
that is not registered, CKli stops and logs the exact dotnet user-secrets set ... command line to run.
The whole scheme (the secret store, the {Prefix}_READ_PAT / {Prefix}_WRITE_PAT derivation, and what
each command actually needs from a token) is documented with its implementation, in
CKli.Core's README.
There are more than 50 of them, but ckli --help is a map rather than a list: one line per depth 1 command
or namespace with a one line summary, and the child names right below a namespace. ckli <command> --help
then details one of them in full — ckli tag --help for a whole namespace, ckli tag push --help for a
single command. (Why the help collapses, and where each summary comes from, is in
CKli.Core's README.)
The table below is that same map, with where each entry is documented.
The intrinsic commands are the ones CKli itself implements. They handle Stack, World, Repo and Git and
know nothing of what a repository contains, so they work on any Git repository. Their reference lives
next to their implementation, in
CKli.Core/CKliCommands/README.md.
Every other command comes from a plugin and is documented with that plugin: a World that doesn't enable
the plugin simply doesn't have the command. What follows is what the 9 Standard Plugins add — another
plugin adds its own, and its commands appear in ckli --help the same way.
| Command | What it does | Reference |
|---|---|---|
branchclose list open push switch sync |
Git branch operations applied to the current Repo or to all the Repos of the World. Opens, closes, switches and synchronizes the branches of the CKli branch model. | BranchModel — except branch push, which is intrinsic, and branch sync/branch list/branch close, which HotZone handles |
build |
Build-Test-Package, keeping the produced packages local. | Build |
*build |
Upstream closure build: builds the producers of the current repositories. | Build |
clone <stackUrl> |
Clones a Stack and the repositories of one of its Worlds in the current directory. | intrinsic |
commit <message> |
Commit any pending changes. Does nothing if there's no change to commit. | BranchModel |
create <stackUrl> |
Creates a new Stack and its remote repository in the current directory. | intrinsic |
depsupdate |
Aligns the external dependencies of the World. | Build |
exec <process-name-and-args> |
Executes an external process on each Repo. | intrinsic |
fetch |
Fetches all branches (and optionally tags) from the remote(s). | intrinsic |
fixbuild cancel info publish push start |
Builds and publishes a Fix Workflow. Starts, shares, inspects and cancels a Fix Workflow. | the Fix Workflow, then HotZone and Build |
issue |
Asks plugins to detect any possible issues. | intrinsic |
layoutfix xif |
Reconciles the World definition file and the folders and repositories that are on the disk. | intrinsic |
log |
Opens the log file of the last run. | intrinsic |
maintenancemigrate rebuild |
Rebuilds versions that have already been released. One-off conversions of repositories created under older CKli conventions. | Build and Migration |
pluginadd compile create disable enable info remove set unset |
Manages the plugins of the current World. | intrinsic |
publish |
Build-Test-Package and publish all the artifacts. | Build and Publish |
*publish |
Upstream closure publish: publishes the producers of the current repositories. | Build and Publish |
pull |
Pulls the Stack repository and all Repo's local branches that track a remote branch. | intrinsic |
push |
Pushes the Stack repository and all Repo's local branches that track a remote branch. | intrinsic |
remotestack |
Operations on the remote repositories rather than on their local clones. | intrinsic |
repoadd create remove |
Adds, creates and removes the Repositories of the current World. | intrinsic |
status |
Lists the World's Repos, or every Stack that exists locally when outside a Stack. | intrinsic |
tagdelete fetch list pull push |
Git tag operations on the current Repo or on all the Repos of the World. | intrinsic |
update |
Auto update CKli (must not be in interactive mode). | intrinsic |
versionbump deprecate |
The CSemVer version tags of a repository: bumping and deprecating. | VersionTag |
worldlock lts reference unlock |
The definition of the current World and the locks its Stack shares. | intrinsic: references, LTS Worlds and locks |
Those of the Build plugin are the two halves of what CKli automates and are worth knowing about even
before enabling anything: build and publish propagate
what a World produces, and
deps update
aligns what it consumes from the outside.
This repository holds the tool and the source of the 9 Standard Plugins. Each project documents its own design:
| Project | |
|---|---|
CKli.Core |
The Stack / World / Repo model, Git, the hosting providers, the secrets store and the plugin system. Its CKliCommands/ folder holds the intrinsic commands and their reference. |
CKli.Loader |
The collectible AssemblyLoadContext the plugins are loaded into, and the assemblies the host shares with them. |
CKli.Plugins.Core |
What a plugin is written against, and the code generation that replaces reflection once plugins are compiled. |
CKli.Testing |
The fixture layer: fake local Git remotes and cloned folders to run real ckli commands with no network. |
StandardPlugins/ |
The 9 Standard Plugins, listed with their READMEs above. |
There are 2 possible approaches to develop and test CKli itself.
The CompileAndInstallLocalCKli.ps1 script fully rebuilds
CK-SVersion and CKLi and installs the tool in the 0.0.0-0 version.
.\CompileAndInstallLocalCKli.ps1When done, reinstall the current version (here the latest prerelease if it exists) from NuGet:
ckli updateThis is possible thanks to the CKli/Properties/launchSettings.json file.
{
"profiles": {
"C:\\Dev3 (ps)": {
"environmentVariables": {
"PATH": "$(SolutionDir)CKli/$(OutputPath);%PATH%"
},
"commandName": "Executable",
"executablePath": "powershell",
"workingDirectory": "$(SolutionDir)../CKliTestFolder"
}
}
}It launches the compiled CKli instance in a CKliTestFolder.
This trick prepends the build output path to the $Env:Path (Linux/Mac: $Path): the ckli.exe is found here rather than in
the %userprofile%\.nuget\packages (Linux/Mac: ~/.nuget/packages).
This only requires to manually create (once) the CKliTestFolder nearby the CKli folder.
In Visual Studio use "Start Without Debugging (Ctrl+F5)": the running terminal can use your already running IDE
instance as the debugger when using the ckli .... --ckli-debug flag.