Skip to content

CK-Build/CKli

Repository files navigation

CKli

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.

⚠️ This is currently under development.

Getting Started

Prerequisites

Installation

CKli is a dotnet tool. You should install it globally by running:

dotnet tool install CKli -g

And auto-update it with:

ckli update

The 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-cache

Run CKli

If you installed CKli globally, you can run ckli in any command prompt to start it.

The basics: Stack-World-Repo

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).

Private & Public stack and repositories

⚠️ Explain PAT.

Core commands

These commands are implemented by CKli.Core. They apply to any Git repositories.

update --stable --prerelease --allow-downgrade

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.

clone <url> --private --allow-duplicate --ignore-parent-stack

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.

create <url> --private

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/.

log --folder

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).

pull --with-tags --all --continue-on-error

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.

fetch --all --with-tags

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.

branch push <branch> --all

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.

push --stack-only --all --continue-on-error

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.

status --by-branch --all

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.

repo add <url> --allow-lts

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.

repo remove <name or url> --allow-lts

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.

layout fix --delete-aliens

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-aliens is specified, repositories not defined in the World are deleted.

layout xif

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.

issue --all --fix

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).

exec ... --ckli-continue-on-error --ckli-all

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-all builds all the Repo of the Stack (the current state of the working folder).
  • ckli exec git pull --tags --force updates all the tags from the remotes, replacing the local ones (kind of ckli tag pull * that is not currently supported).

Tag commands (list, fetch, pull, push, delete)

tag list --local --remote --all

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).

tag fetch --all

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).

tag pull <tag names> --allow-multi-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.

tag push <tag names> --allow-multi-repo

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.

tag delete <tag names> --with-remote --remote-only --allow-multi-repo

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.

Plugin commands (info, create, add, remove, enable)

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.

plugin info --compile-mode --force

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.

plugin create <name> --allow-lts

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.

plugin add <packageId@version> --allow-lts

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.

plugin remove <name> --allow-lts

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").

⚠️ Warnings:

  • 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.

plugin disable <name>

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.

plugin enable <name>

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.


Development & Local Testing

There are 2 possible approaches to develop and test CKli itself.

Temporarily replaces the currently installed CKli tool.

# 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 CKli

Using an independent context (thanks to launchSettings.json).

This 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors