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 a dotnet tool. You should install it globally by running:
dotnet tool install CKli -gAnd auto-update it with:
ckli updateThe update command is described below and should handle switching from production and pre-releases easily.
Unfortunately, there are some issues with dotnet tool update (that ckli update runs): to switch to the last
pre-release of CKli, use:
dotnet tool uninstall CKli -g
dotnet tool update CKli -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.
A World is a set of Git repositories. The set is described by a simple XML file that lists the repositories and can organizes them in a folder structure:
<CK-Build>
<Repository Url="https://github.com/CK-Build/CSemVer-Net" />
<Repository Url="https://github.com/CK-Build/SGV-Net" />
<Folder Name="Cake">
<Repository Url="https://github.com/CK-Build/CodeCake" />
</Folder>
</CK-Build>This definition file is stored in the main branch of a Stack repository:
ckli clone https://github.com/CK-Build/CK-Build-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 a World (typically the default one).
These commands are implemented by CKli.Core. They apply to any Git repositories.
Auto updates CKli with a newer available version if it exists. Use ckli --version to display the
currently installed version.
By default, this will lookup for a stable version if the current version is a stable one.
With --stable, stable versions only will be considered even if the current version is a prerelease.
With --prerelease, prerelease versions will be considered (including CI builds) even if the current version is stable.
The --allow-downgrade flags allows package downgrade. This is useful to come back to the last stable version when
the current version is a pre release.
This command transparently updates the CKli version used by the CKli.Plugins solution. If a Tests/Plugins.Tests project
exists, the version of the CKli.Testing package reference is also updated.
Clones a Stack and all its current World repositories in the current directory.
--private drives the name of the Stack repository folder: it is .PrivateStack/
instead of .PublicStack/.
--allow-duplicate must be specified if the same Stack has already been cloned
and is available on the local system. In such case, the Stack's folder name will be
Duplicate-Of-XXX/ instead of XXX/.
--ignore-parent-stack allows the cloned Stack to be inside an existing one.
Creates a new Stack by creating the remote repository (the url must belong to a Git hosting provider that CKli can handle), checks out the new stack in the current directory, initializes default files in the stack folder and pushes it.
The <url> must end with the -Stack suffix.
--private uses .PrivateStack/ folder instead of .PublicStack/.
Opens the last log file. When --folder (or -f) is specified, the folder is opened instead
of the last log file.
The Log/ folder is %LocalAppData%/CKli/Out-of-Stack-Logs/ when CKli doesn't start is a Stack folder, otherwise
each Stack keeps its own logs in their .PublicStack/Logs (or .PrivateStack/Logs).
Pulls (fetch-merge) the Stack repository and all current Repos' local branches that track a remote branch.
By default, remote tags are safely fetched, preserving local tags (see cki tag fetch). When --with-tags
is specified, a ckli tag pull * is done that blindly replaces local tags.
By default, the current directory selects the Repos unless --all is specified.
Any merge conflict is an error. Unless --continue-on-error is specified, the first error stops the operation.
A pull (without tags) is implicitly executed first by ckli push.
Fetches all branches (and optionally the tags that are associated to any fetched objects) in the current Repos.
By default, the current directory selects the Repos unless --all is specified.
When --with-tags is specified, the fetched remote tags will replace locally defined tags if
they reference the same object. If a local tag references a different object, this will be an error.
Use ckli tag list to detect conflicts.
Pushes the specified branch to its remote "origin", creating it if it doesn't exist yet. The branch is fetched and must be successfully merged before the push can succeed.
By default, the current directory selects the Repos unless --all is specified.
When applied to multiple Repos, a warning is emitted if the branch doesn't exist in a Repo.
Pushes the Stack repository and all Repo's local branches that track a remote branch. A pull is done before: it must be successful for the actual push to be done.
Tags are not pushed: tags are pushed when artifacts are published and this is the job of dedicated plugins.
When --stack-only is specified, only the Stack repository is pushed. Repos are ignored.
By default, the current directory selects the Repos unless --all is specified.
Any conflict is an error. Unless --continue-on-error is specified, the first error stops
the push.
When the current directory is in a World, lists the Repos with their folder path, current branch name, remote commit diffs, and remote origin url. Otherwise, this lists all the Stacks that are registered on
When --by-branch (or -b) is specified, repositories are grouped by their current branch name
instead of being listed in definition order.
Adds a new repository to the current world.
If the current World is a LTS one (CK@Net8), --allow-lts must be specified because
it is weird to add a new Repo to a Long Term Support World.
This clones the repository in the current directory, updates the World's definition file in
the Stack repository and creates a commit. To publish this addition, a push (typically
with --stack-only) must be executed.
Removes an existing Repo from the current world.
If the current World is a LTS one (CK@Net8), --allow-lts must be specified because
it is weird to remove a Repo from a Long Term Support World.
This deletes the local repository, updates the World's definition file in
the Stack repository and creates a commit. To publish this removal, a push (typically
with --stack-only) must be executed.
Compares the local layout of folders and repositories with the World's definition file and updates the local file system accordingly.
- Folders are moved and/or renamed to match the definition file (also fixes the difference in name casing).
- Missing Repo are cloned (where they must be).
- If
--delete-aliensis specified, repositories not defined in the World are deleted.
Opposite of fix: consider the current folders and repositories to be the "right" definition of the World.
The World's definition file is updated and a commit is done in the Stack repository.
To publish this update, a push (typically with --stack-only) must be executed.
Detects issues and display them or fix the issues that can be automatically fixed when --fix is specified.
When --all is specified, this applies to all the Repos of the current World (even if current path is in a Repo).
This command execute any external process on the current Repo (or all of them if --ckli-all is specified).
By default, whenever a process fails on a repository (by returning a non 0 exit code), the loop stops: use
--ckli-continue-on-error flag to not stop on the first error.
The flags --ckli-continue-on-error and --ckli-all are not submitted to the process command line. (They are prefixed
by --ckli- to avoid a name clash with an existing process argument.)
Examples:
ckli exec dotnet build --ckli-allbuilds all the Repo of the Stack (the current state of the working folder).ckli exec git pull --tags --forceupdates all the tags from the remotes, replacing the local ones (kind ofckli tag pull *that is not currently supported).
Lists local tags and/or remote tags from the current Repo or all the Repos.
By default (without --local or --remote), both local and remote tags are fetched
and a diff is displayed showing tags that exist only locally, only remotely, or in both.
When --local is specified, only local tags are listed.
When --remote is specified, only remote tags are listed.
When --all is specified, lists tags for all the Repos of the current World
(even if the current path is in a Repo).
Safe "tag pull" that preserves local tags and local tags in conflicts. This creates only new local tags.
When --all is specified, lists tags for all the Repos of the current World
(even if the current path is in a Repo).
Pulls the specified tags from the remote "origin" into the current Repo. Local modifications of fetched tags are lost, conflicts are solved: remote always wins.
Tag names must contain only ASCII characters with lowercase letters (to avoid case sensitivity issues).
They can be the "canonical names" that starts with "refs/tags/" or simple names (like "v4.0").
The very basic glob capabilities of https://git-scm.com/docs/git-fetch are supported: ckli tag pull *
pulls all the remote tags (this is equivalent to git pull --tags --force).
Must be run from within a Repo directory unless --allow-multi-repo is specified.
Pushes the specified tags from the current Repo to its remote "origin". Modifications of remote tags are lost (the local version replaces them).
Tag names must contain only ASCII characters with lowercase letters (to avoid case sensitivity issues).
Must be run from within a Repo directory.
Must be run from within a Repo directory unless --allow-multi-repo is specified.
Deletes local tags (and/or optionally from the remote "origin"). The operation is idempotent: tags that don't exist are silently ignored.
By default, only local tags are deleted and the command must be run from within a single Repo.
--with-remote deletes tags from both local and remote.
--remote-only deletes remote tags only, keeping local ones.
--allow-multi-repo allows the command to proceed when the current path is above multiple Repos.
By default, the current path must be within a single Repo.
The core commands of CKli handles Stack, World and Repo (Git repositories). The Repo can contain anything. To handle tasks specific to a technology (.NET, Node, Ruby, etc.) external and optional plugins can be used.
Plugins are written in .NET and distributed as NuGet packages or can be source code directly in the Stack repository.
Provides information on installed plugins, their state, Xml configuration element and an optional message that can be produced by the plugin itself.
--force (or -f) forces plugin recompilation even if the compile mode hasn't changed.
--compile-mode is an advanced option to be used when developing plugins. Plugins are discovered
once (after a creation, an install or a removal) via reflection and then compiled in Release
with generated code that replaces all the reflection.
A regular load is just an Assembly.Load (in a collectible AssemblyLoadContext) and a call to an
initialization function that initializes the graph of objects (command handlers, Plugin description, etc.).
In very specific scenario (developing, debugging), it is possible to set the compile mode to None (plugins
are not compiled, reflection is always used) or Debug to compile the plugins in debug configuration.
Creates a new source based plugin project in the current World.
The name can be a short name ("MyFirstOne") or a full plugin name ("CKli.MyFirstOne.Plugin").
In a public World named "MyWorld", the code of the plugin is created in the .PublicStack/MyWorld-Plugins/Ckli.MyFirstOne.Plugin/ folder.
It can be edited and tested freely.
The new plugin is added to the <Plugins /> element of the world definition file:
<MyWorld>
<Plugins>
<MyFirstOne />
</Plugins>
<!-- Folders and Repositories... -->
</MyWorld>The <MyFirstOne /> element is the plugin configuration: the plugin code can read it to configure
its behavior and update it.
The new plugin will be "published" when push (typically with --stack-only) is executed.
If the current World is a LTS one (CK@Net8), --allow-lts must be specified because
it is weird to add a new plugin to a Long Term Support World.
Adds a new packaged plugin in the current World or updates its version.
When added, the plugin is added to the <Plugins /> element of the world definition file,
just like in the source based scenario.
If the current World is a LTS one (CK@Net8), --allow-lts must be specified because
it is weird to add a new plugin to a Long Term Support World.
The new plugin will be "published" when push (typically with --stack-only) is executed.
Removes a source based or package plugin from the current World.
The name can be the short name ("MyPlugin") or the full plugin name ("CKli.MyPlugin.Plugin").
- The removed plugin must not have dependent plugins otherwise this fails (and nothing is done).
- The plugins must not be globally disabled (see below).
If the current World is a LTS one (CK@Net8), --allow-lts must be specified because
it is weird to remove a plugin from a Long Term Support World.
Plugins are enabled by default but can be disabled.
A IsDisabled="true" attribute is set on the corresponding plugin configuration element.
<MyWorld>
<Plugins>
<MyPlugin IsDisabled="true">
<SomeOption>None</SomeOption>
</MyPlugin>
<MyPlugin />
</Plugins>
<!-- Folders and Repositories... -->
</MyWorld>As usual, this modification will be "published" when push (typically with --stack-only) is executed.
Reverts the plugin disable command by removing the IsDisabled="true" attribute on the plugin configuration element.
As usual, this modification will be "published" when push (typically with --stack-only) is executed.
There are 2 possible approaches to develop and test CKli itself.
# 1. Build and pack
dotnet build CKli.sln -c Debug
dotnet pack CKli/CKli.csproj -c Debug
# 2. Install as global tool (uninstall first if already installed)
dotnet tool uninstall -g CKli
dotnet tool install -g CKli --source ./CKli/bin/Debug --version 0.0.0-0
# 3. Test your changes...
ckli --help
ckli log
# 3bis. ..or enter debug mode before the command handling:
ckli clone https://github.com/acme-corp/My-Stack --ckli-debug
# 4. When done, reinstall from NuGet
dotnet tool uninstall -g CKli
dotnet tool install -g CKliThis 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.