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 informations in a single place.
CKli is a dotnet tool. You should install it globally by running:
dotnet tool install CKli -gAnd update it with:
dotnet tool update <PACKAGE_ID> -gTo use a CI build instead of the last stable release, specify the version and the Azure source feed:
dotnet tool update CKli -g --version 0.0.8--0007-dev --source https://pkgs.dev.azure.com/Signature-OpenSource/Feeds/_packaging/NetCore3/nuget/v3/index.jsonIf 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.
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/.
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).
Resynchronizes the current Repo or World from the remotes.
When the current directory is in a Repo, only the current repository is pulled
unless --all is specified.
When --skip-pull-stack is specified, the Stack repository is not updated.
Fetches all branches of the current Repo or all the Repos of the current World.
When the current directory is in a Repo, only the current repository's branches are
fetched unless --all is specified.
--from-all-remotes fetches from all remotes instead of the 'origin' remote.
Pushes the current Repo or all the current World's Repos current branches to their remotes.
Any conflict is an error, unless --continue-on-error is specified, the first error stops
the push.
When the current directory is in a Repo, only the current Repo's current branch is
pushed unless --all is specified.
When --stack-only is specified, only the Stack repository is pushed. Current Repo and
World is ignored.
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 repsoitory 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.)
Example: ckli exec dotnet build --ckli-all builds all the Repo of the Stack.
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 informations on installed plugins, their state, Xml configuration element and an optional message that can be produced by the plugin itself.
--compile-mode is an advanced option to be used when developping 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 objetc (command handlers, Plugin description, etc.).
In very specific scenario (developping, 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.
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.
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.
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.