This document describes the tools, tasks, and workflows that one needs to be familiar with in order to effectively maintain this project. If you use this project for your own development as is but don't plan on modifying it, this guide is not for you. You can of course keep reading though.
Outline of the guide:
- Tools: the development tooling for this codebase
- Project layout: an overview of project directories
- Tasks: common things done during development
- Releases: when releasing the latest changes
- Workflow: around changes and contributions
- Everything else: and all of those other things
Most development is done with the Go programming language.
To get started as a maintainer, you'll need to install Golang. We tend
to use the latest version available but our minimum version is defined in the
go.mod file.
We recommend using the official installer to install the matching version.
Sometimes setups break but please don't fear. Solutions remain in reach.
Finding and setting the GOPATH
The GOPATH is an environment variable that determines where go checks for
certain files, so correct values are needed to run certain Makefile commands.
If packages for this project fail to be found during setup, checking these might be a good place to start!
First, find your GOPATH. The easiest way to run the following commands:
go env | grep GOPATHNext, update your GOPATH environment variables in your ~/.zshrc
(learn how for other terminals like bash) to point to your
installation:
# Set variables in .zshrc file
# NOTE - your `GOPATH` may be different
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/binUpdating the GOOS and GOARCH
Compiling the project might work just fine but an attempt to run the build can
error with something like an exec format error.
This happens if you're compiling for the wrong target machine and can be fixed
by adjusting your GOOS and GOARCH environment variables to match the current
system you're using.
Uninstalling a Homebrew installation
We don't recommend installing Golang with Homebrew because it can be difficult to choose a specific version and set the proper paths.
Uninstall Golang from Homebrew:
brew uninstall goLinters are used to help catch formatting strangeness and strange spellings.
The CI runs linting via golangci-lint and is configured in the
.golangci.yml file.
Install this tool to mimic CI environments and run linting with the Makefile:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/binConfirm the installation worked right with:
golangci-lint --versionTroubleshooting
If you receive the following error:
$ golangci-lint --version
command not found: golangci-lintYou may need to update your $PATH environment variable with $GOPATH/bin:
-
Open your terminal's configuration file (e.g.
~/.zshrc):$ vim ~/.zshrc -
Add the following:
export PATH=$PATH:$(go env GOPATH)/bin
-
Open a new terminal session (tab) or source your configuration file (
source ~/.zshrc). -
Run the following command:
$ golangci-lint --version
Settings are already defined in this project for using the VSCode editor with
golangci-lint as the go.lintTool. Inspect .vscode/settings.json
for more details.
Linting troubles can happen from unexpected formatting or a broken installation. Broken installations might be fixed by the below:
Matching versions that don't match
The latest version of golangci-lint is used in testing and should be matched
by the installation you have.
Compare the latest version to your current installation and try linting once more.
Uninstalling a Homebrew installation
Setting the correct version of golangci-lint can be difficult with Homebrew.
If this is causing issues try uninstalling it with:
brew uninstall golangci-lintCommon commands are contained in the Makefile for this project. These perform
tasks such as building and testing and releasing.
More details will follow these different tasks but you should have make ready:
make --versionOur project layout is based on golang-standards, Effective Go, and Practical Go.
bin/ ........ build artifacts including the compiled CLI binary
cmd/ ........ commands some logic to process inputs and outputs
dist/ ....... release artifact that outputs the signed binaries
internal/ ... private packages that contain shared logic for `cmd`
scripts/ .... files for downloading and installing released builds
test/ ....... test data and test helper packagesThe build artifact from make build that outputs the compiled CLI binary.
Defines each command that is available to the command-line tool.
Think of this as the user's interface into this command-line tool. Each command
processes input from stdin then outputs information to stdout and stderr
as certain actions are performed.
The control flow of a command is contained with that command, while some details
and execution implementations are delegated to pkg/.
The file structure maps directly to the command, such as:
$ slack auth login # full command
$ slack login # shortcut command
cmd/
|__ auth/
|___ login.goThe release build artifacts from make build-snapshot that includes binaries
and archives for all platforms.
An installation of goreleaser is required and the latest version
is recommended.
A safe place to store private packages used by commands and this program. Shared logic and implementation specifics are contained within since these packages are not exported.
Some detailed command implementations extend into these internal packages when the implementations are detailed. This is meant for command logic that is needed to perform the command, but might not make sense to inline for some reason such as testing.
Think of this as the back-end for a command. Each package should implement logic for a single command, but delegate input and outputs to the command front-end.
The file structure maps directly to the command and should usually match cmd/,
such as:
$ slack auth login # full command
$ slack login # shortcut command
cmd/
|__ auth/
| |__ login.go
pkg/
|__ auth/
|__ login.goInstallation and setup scripts for various operating systems are available here.
This is home to test data, helpers, and anything else test related. The only
exception are the unit test files, which sit next to the file they test with the
suffix _test.go.
Certain things are common during development and require a few commands.
Task outline:
- Cloning the project
- Initializing the project
- Testing
- Building
- Running
- Generating documentation
- Versioning
- Updating
- Deprecating features and flags
- Allowlist configuration
git clone https://github.com/slackapi/slack-cli.git
cd slack-cli/When you first clone the project or after you major updates to the project, it's a good idea to run our initialization script. It will fetch the latest dependencies, tags, and anything else to keep you up-to-date:
make initTo ensure that certain code paths are working as we might expect, we use a suite of module-based unit tests and complete end-to-end tests, along with a healthy dose of manual testing and dogfooding.
Each module has unit tests in the same directory as the code with the suffix
_test.go (i.e. version_test.go).
You can run the entire unit test suite using the command:
make testYou can also add the testdir argument to specify which directories to test and
testname to pick the name of tests to run:
make test testdir=cmd/auth testname=TestLogoutCommandNote: Test code should be written in syntax that runs on the oldest supported Go
version defined in
go.mod. This ensures
that backwards compatibility is tested and the APIs work in versions of Golang
that do not support the most modern packages.
To get a visual overview of unit test coverage on a per-file basis:
make test # run the test suite
make coverage # open coverage summaryA suite of end-to-end tests that cover common commands with the CLI, samples, and the client can be found in the private slackapi/platform-devxp-test repo. Please see that repo for details on how to run the end-to-end tests locally.
These tests take about 5 minutes to complete and the test results will be reported
on CircleCI. At this time only members of the slackapi GitHub org can access test
results directly.
All tests are run during PRs, merges to main, and on nightly builds of main
by our continuous integration systems: GitHub Action and CircleCI workflows.
The module tests that are used can be found alongside the modules in *_test.go
files and edited as needed.
The end-to-end tests are located in the slackapi/platform-devxp-test repo and can be adjusted to new requirements by a maintainer.
By default the tests execute with the dev-build GitHub Release of this repo.
Branches on this repo will also create a related GitHub Release, and end-to-end
tests will use the branch-specific Slack CLI release to execute tests.
The branch name can also be set by changing
the e2e_target_branch
for the build-lint-test-e2e-test workflow in the .circleci/config.yml file,
but take care not to merge this change into main!
If you'd like to add tests, please review our contributing guide to learn how to open a discussion and pull request. We'd love to talk through setting up tests!
Updating tests in the end-to-end testing repo can be done by a maintainer through opening a pull request to that repo with the changes.
This program is compiled into a single, executable binary. Before building, the
build script will remove past artifacts with make clean:
make buildYou can run the executable binary with:
./bin/slack --versionIf you have installed the official release of the CLI, then you will now have two binaries:
slack --version # this is the official release
./bin/slack --version # this is the local development buildSome developers like to use the local development build as their globally
installed binary, so that they do not need to prefix the binary with ./bin/.
You can do this in a couple of different ways:
If /usr/local/bin/slack already points a different binary, you can choose to
update it globally.
which slack # Check the path for slack
slack # Check what program executes when you run slackDelete the official release from /usr/local/bin/slack if you installed it in
the past:
rm /usr/local/bin/slackNext, update your $PATH to include your local build by opening your ~/.zshrc
or ~/.bash_profile and adding the following:
# Replace `path/to/slack-cli` with your project's full path
# You can find the path by running `pwd` inside the slack-cli/ directory
export PATH=path/to/slack-cli/bin:$PATHThen, refresh your terminal session:
source ~/.zshrc
# or, source ~/.bash_profile for bash terminals
# or, just close and re-open your terminalFinally, verify that you have correctly set up your $PATH by running the CLI:
slack --versionCreate a symbolic link to a build of this program with the following command and changes:
- Replace
/path/to/with your path - Replace
slack-cli-localwith your preferred name to reference the compiled binary
ln -s ~/path/to/slack-cli/bin/slack /usr/local/bin/slack-cli-localVerify that everything is linked correctly with a test in the home directory:
cd
slack-cli-local --versionYou can generate documentation for all commands in the docs/ directory with:
slack docgenAutomated workflows run on documentation changes to sync files between this project and the documentation build.
A GitHub application called @slackapi[bot] mirrors changes
to these files and requires certain permissions:
- Actions: Read and write
- Contents: Read and write
- Metadata: Read
- Pull requests: Read and write
Access to both this project repo and documentation repo must also be granted.
Credentials and secrets for the app can be stored as the following variables:
GH_APP_ID_DOCSGH_APP_PRIVATE_KEY_DOCS
We use git tags and semantic versioning to version this program. The
format of a tag should always be prefixed with v, such as v0.14.0.
Changes behind an experiment flag should be semver:patch. This is because
experimental features don't impact regular developers and experiments are
not documented. Once the experimental flag is removed, a semver:patch,
semver:minor, or semver:major version should be tagged.
The version is generated using git describe and contains some helpful
information:
./bin/slack-cli-local --version
slack-cli-local v0.14.0-7-g822d09a
# v0.14.0 is the git tag release version
# 7 is the number of commits this build is ahead of the v0.14.0 tag
# g822d09a is the git commit for this buildPushing a new version with git tag and a semantic version creates a release
for download with the installation scripts. Check out
Development Build.
Internal runbooks detail the process for official releases of stable versions.
The code this project relies upon have occasional updates that can be pulled in with some processes.
New versions of the Go language are released with great features that can be useful in development. Updates for the latest are checked every day for both the latest minor or patch release.
A pull request is created once a newer version is found, attempting to update the specific references language references we have. Human checks should happen to ensure all references were updated properly:
- The Go mod file in:
go.mod- e.g.go 1.22.1 - CircleCI runners in:
.circleci/config.yml- e.g.cimg/go:1.22.1 - GitHub Actions in:
.github/workflows/tests.yml- e.g.actions/setup-go
Automation that powers can be found in this workflow and this app. Secrets are found elsewhere.
For these changes to complete, certain application permissions are needed:
- Actions: Read and write
- Contents: Read and write
- Metadata: Read
- Pull requests: Read and write
- Workflows: Read and write
Access to this project is also required with the selected application scopes.
Credentials and secrets for the app can be stored as the following variables:
GH_APP_ID_RELEASERGH_APP_PRIVATE_KEY_RELEASER
Dependencies are often updated with new and cool things too. Most of these are found with the help of @dependabot but more updates remain possible with:
- Update the module version in
go.mod - Run
go mod tidyto update the modules and thego.sum
To see the version of a dependency this module uses, or to see the dependency tree of a transitive dependency, this command can be helpful:
go mod graph | grep <module name>The goreleaser package we use to build release snapshots needs
updates in the following files on occasion:
.circleci/config.yml.goreleaser.yml
Testing in our CI setup uses changes to these files when creating test builds.
Many good things come to an end. This can sometimes include commands and flags. When commands or flags need to be removed, follow these steps:
Deprecating features
- Public functionality should be deprecated on the next
semver:majorversion- Add the comment
// DEPRECATED(semver:major): Description about the deprecation and migration path - Print a warning
PrintWarning("DEPRECATED: Description about the deprecation and migration path")
- Add the comment
- Internal functionality can be deprecated anytime
- Add the comment
// DEPRECATED: Description about the deprecation and migration path
- Add the comment
- Please add deprecation comments generously to help the next person completely remove the feature and tests
Deprecating commands
- Display a deprecation notice on the command with the
Deprecatedattribute - Add a deprecation warning to the
Longhelp message - Optionally hide the command from help menus with the
Hiddenattribute - Somehow mark the command for removal in the next major version
Deprecating flags
- Print a message that says the flag is deprecated whenever it is used
- Recommend alternative flags if available (ex:
internal/config/flags.go) - Hide the flag from help menus with the
.Hiddenattribute - Optionally use the recommended flag whenever possible
- Somehow mark the flag for removal in the next major version
The following inbound/outbound domains are required for Slack CLI to function properly:
Slack CLI:
- api.slack.com
- downloads.slack-edge.com
- slack.com
- slackb.com
On regular occasion and a recurring schedule the latest changes are tagged for release.
The development build comes in 2 flavours:
- Development build GitHub release
- Development build install script
A development build and recent changelog is generated each night from main
with all of the latest changes. Builds are released with the dev-build tag and
can be reviewed from the releases page.
Each release page contains:
- The commit used when building the development build and evidence of a tag created in past versions
- A changelog containing commit messages made since the last tag with a semantic
version
v*.*.*pattern - Assets for the macOS, Linux, and Windows binaries
The development build and release automation is performed from the deploy-dev
job in the .circleci/config.yml file.
An installation script for the development build provides the same dev-build
release tag but with magic setup:
curl -fsSL https://downloads.slack-edge.com/slack-cli/install-dev.sh | bash -s slack-cli-devChanges to the actual installation scripts are made through other channels and
might become outdated between releases. These scripts can still be found in the
scripts/ directory in the meantime.
Unreleased changes that haven't landed on main can be shared and installed
using feature tags.
Create a new tag in the following format on a branch of a pull request:
git tag v3.4.5-[example-branch-name]-feature # Replace with the branch
git push origin v3.4.5-feat-something-feature # Start the build on pushAfter a few minutes these changes can be installed using the install script:
curl -fsSL https://downloads.slack-edge.com/slack-cli/install-dev.sh | bash -s -- -v 3.4.5-feat-something-featureDevelopment on a forked branch is common for contributors to this project and
encouraged for submitting contributions. Read CONTRIBUTING.md
for instructions on contributing.
Maintainers might prefer to push to the origin repostiory for access to secret environment variables present in CI workflows. This is fine but with care taken to use branches.
main is where active development occurs.
Development should happen on branches created off of main or other branches
and namespaced according to the change being made to help remain organized.
A conventional commit pattern is recommended to help group branches
that are making changes to similar parts of the codebase - e.g.
fix-config-auth-id-missing or feat-command-help-interactive.
Sharing branch names can be useful for collaboration on long-running features and is recommended for pulling changes from others without pushing to the same remote or pull request. Otherwise branch naming is unique to the change being made.
After a major version increment, there also may be maintenance branches created specifically for supporting older major versions.
Curious or promising feature work might prefer to remain behind an experiment
flag to avoid changing current command behaviors while still merging into the
main branch.
Experiments are introduced in internal/shared/experiment/experiment.go and
follow the kebab-case-format, while implementation of features can be gated with
the config.WithExperimentOn utility.
Run slack --help --verbose to view all active experiments.
Learn more about how experiments are versioned in the Versioning section.
Labels are used to run issues through an organized workflow. Here are the basic definitions:
bug: A confirmed bug report. A bug is considered confirmed when reproduction steps have been documented and the issue has been reproduced.build: A change to the build, compile, or CI/CD pipeline.changelog: An issue or pull request that should be mentioned in the public release notes or CHANGELOG.code health: An issue or pull request that is focused on internal refactors or tests.discussion: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues.docs: An issue that is purely about documentation work.duplicate: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number.enhancement: A feature request for something this package might not already do.experiment: A change that is accessed behind the --experiment flag or togglegood first issue: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known.needs info: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information.question: An issue that is like a support request because the user's usage was not correct.security: An issue that has special consideration for security reasons.semver:major: A change that requires a semver major release.semver:minor: A change that requires a semver minor release.semver:patch: A change that requires a semver patch release.server side issue: An issue that exists on the Slack Platform, Slack API, or other remote endpoint.
Triage is the process of taking new issues that aren't yet "seen" and marking them with a basic level of information with labels. An issue should have one of the following labels applied:
bugbuildcode healthdiscussiondocsenhancementneeds infoquestion
Hint: The main triage issues always have a description that starts with M-T:
Issues are closed when a resolution has been reached or the issue becomes stale. If for any reason a closed issue seems relevant once again, reopening is great and better than creating a duplicate issue.
Opened pull requests should be triaged by a maintainer. The purpose of pull request triage is to keep the project healthy, organized, and to help simplify releases. The expectation is that maintainers triage their own pull requests and those from outside contributors. These are transferrable practices that are followed by most of Slack's open sourced SDKs and Frameworks.
Steps to triage a pull request:
- Reviewers:
- Minimum of 1 reviewer
- Assignees:
- Minimum of 1 assignee
- Assignees are usually the people who are committing changes
- Labels:
- One of the following main labels to describe the type of pull request:
- Main labels always have a description that starts with
M-T:- Example:
enhancement,bug,discussion,documentation,code health - Note: The main labels are used to organize the automated CHANGELOG
- Example:
- The
changeloglabel denotes changes to document in the public Slack API Docs release notes - Learn about labels in the Issue management section
- Main labels always have a description that starts with
- A semver label
- Semver labels are used to determine the next release's version
- Example:
semver:major,semver:minor, orsemver:patch - Learn more about semver in the Versioning section
- One of the following main labels to describe the type of pull request:
- Milestone:
- A milestone should be assigned when possible, usually as the
Next Release - After a release, the
Next Releasemilestone is renamed to the tagged version
- A milestone should be assigned when possible, usually as the
Steps to merge a pull request:
- Tests must pass on the pull request using the continuous integration suite
- Tests for development APIs are optional, but we recommend investigating why it's failing before merging
- End-to-end tests for pull requests from forks can be started if:
- The
workflowfrom the main branch is used - The
branchcontains the pull request number as:pull/123/head - The
statusis reported with the commit checks
- The
- Code is reviewed and approved by another maintainer
- Title is descriptive
- This becomes part of the CHANGELOG, so please make sure it's meaningful to developers
- References original issue when possible
- Follows the conventional commit format
- Squash and merge the pull request
- Anyone can merge the pull request, but usually it's the creator, assignees, or reviewers
- Commit message should reference original issue and pull request in commit
When in doubt, find the other maintainers and ask.