diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index a49980c..b61011d 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -155,3 +155,6 @@ jobs:
with:
tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ needs.release.outputs.release-tag }}
push: true
+ build-args: |
+ VERSION=${{ needs.release.outputs.release-tag }}
+ POSTHOG_PUBLIC_API_KEY=${{ vars.POSTHOG_WRITE_PUBLIC_KEY }}
diff --git a/.sauced.yaml b/.sauced.yaml
index 4726374..946586d 100644
--- a/.sauced.yaml
+++ b/.sauced.yaml
@@ -14,4 +14,3 @@ attribution:
- nick@opensauced.pizza
zeucapua:
- coding@zeu.dev
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 661c4ff..be5e3f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,39 @@
> All notable changes to this project will be documented in this file
+## [1.5.0-beta.3](https://github.com/open-sauced/pizza-cli/compare/v1.5.0-beta.2...v1.5.0-beta.3) (2024-09-16)
+
+
+### 🍕 Features
+
+* Cut 2.0.0 release ([#193](https://github.com/open-sauced/pizza-cli/issues/193)) ([278a833](https://github.com/open-sauced/pizza-cli/commit/278a8339c701be1f25db9e3e039f727927b4cdef))
+
+## [1.5.0-beta.2](https://github.com/open-sauced/pizza-cli/compare/v1.5.0-beta.1...v1.5.0-beta.2) (2024-09-13)
+
+
+### 🍕 Features
+
+* Update README docs ([#186](https://github.com/open-sauced/pizza-cli/issues/186)) ([99328aa](https://github.com/open-sauced/pizza-cli/commit/99328aaa322b6dc4abe3f1933b6e77ffedecb355))
+
+## [1.5.0-beta.1](https://github.com/open-sauced/pizza-cli/compare/v1.4.1-beta.1...v1.5.0-beta.1) (2024-09-12)
+
+
+### 🍕 Features
+
+* pizza generate insight command ([#179](https://github.com/open-sauced/pizza-cli/issues/179)) ([7315a1d](https://github.com/open-sauced/pizza-cli/commit/7315a1d2ae2764dcbdfd1ce0af1272fde36b1a0c))
+
+
+### 🐛 Bug Fixes
+
+* Config path prefers local dir vs. home dir ([#184](https://github.com/open-sauced/pizza-cli/issues/184)) ([859446a](https://github.com/open-sauced/pizza-cli/commit/859446ac865e7a4c7d7779a2620c7391b4aabf33))
+
+## [1.4.1-beta.1](https://github.com/open-sauced/pizza-cli/compare/v1.4.0...v1.4.1-beta.1) (2024-09-12)
+
+
+### 🐛 Bug Fixes
+
+* Don't overwrite ldflags in justfile builds ([#171](https://github.com/open-sauced/pizza-cli/issues/171)) ([e024687](https://github.com/open-sauced/pizza-cli/commit/e0246879d7d72095f30305cf805ad86e2df1f623))
+
## [1.4.0](https://github.com/open-sauced/pizza-cli/compare/v1.3.0...v1.4.0) (2024-09-11)
diff --git a/CODEOWNERS b/CODEOWNERS
index ab3a50d..55afa11 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,101 +3,108 @@
# Generated with command:
# $ pizza generate codeowners pizza-cli/ --tty-disable true
-.env @jpmcb
+.env @jpmcb @zeucapua
.github/ISSUE_TEMPLATE/bug_report.yaml @jpmcb
.github/ISSUE_TEMPLATE/config.yaml @jpmcb
.github/ISSUE_TEMPLATE/feature_request.yaml @jpmcb
.github/workflows/auto-add-to-project.yml @jpmcb
-.github/workflows/pizza.yml @nickytonline @jpmcb
-.github/workflows/release.yaml @jpmcb @nickytonline
-.github/workflows/test.yaml @jpmcb
-.golangci.yaml @jpmcb
-.sauced.yaml @nickytonline @jpmcb
-CHANGELOG.md @jpmcb @nickytonline @brandonroberts
-CODEOWNERS @nickytonline @jpmcb
-Dockerfile @jpmcb @nickytonline
+.github/workflows/pizza.yml @nickytonline @jpmcb @zeucapua
+.github/workflows/release.yaml @jpmcb @nickytonline @zeucapua
+.github/workflows/test.yaml @jpmcb @zeucapua
+.golangci.yaml @jpmcb @zeucapua
+.sauced.yaml @jpmcb @nickytonline @zeucapua
+CHANGELOG.md @jpmcb @nickytonline @zeucapua
+CODEOWNERS @jpmcb @nickytonline
+Dockerfile @jpmcb @nickytonline @zeucapua
Makefile @jpmcb
README.md @jpmcb
-api/auth/auth.go @jpmcb
+api/auth/auth.go @jpmcb @zeucapua
api/auth/success.html @nickytonline @jpmcb
-api/client.go @jpmcb
+api/client.go @jpmcb @zeucapua
api/mock/mock.go @jpmcb
-api/services/contributors/contributors.go @jpmcb
-api/services/contributors/contributors_test.go @jpmcb
+api/services/contributors/contributors.go @jpmcb @zeucapua
+api/services/contributors/contributors_test.go @jpmcb @zeucapua
api/services/contributors/spec.go @jpmcb
-api/services/histogram/histogram.go @jpmcb
-api/services/histogram/histogram_test.go @jpmcb
+api/services/histogram/histogram.go @jpmcb @zeucapua
+api/services/histogram/histogram_test.go @jpmcb @zeucapua
api/services/histogram/spec.go @jpmcb
-api/services/repository/repository.go @jpmcb
-api/services/repository/repository_test.go @jpmcb
+api/services/repository/repository.go @jpmcb @zeucapua
+api/services/repository/repository_test.go @jpmcb @zeucapua
api/services/repository/spec.go @jpmcb
api/services/spec.go @jpmcb
-api/services/workspaces/spec.go @jpmcb
-api/services/workspaces/userlists/spec.go @jpmcb
-api/services/workspaces/userlists/userlists.go @jpmcb
-api/services/workspaces/userlists/userlists_test.go @jpmcb
-api/services/workspaces/workspaces.go @jpmcb
-api/services/workspaces/workspaces_test.go @jpmcb
+api/services/workspaces/spec.go @jpmcb @zeucapua
+api/services/workspaces/userlists/spec.go @jpmcb @zeucapua
+api/services/workspaces/userlists/userlists.go @jpmcb @zeucapua
+api/services/workspaces/userlists/userlists_test.go @jpmcb @zeucapua
+api/services/workspaces/workspaces.go @jpmcb @zeucapua
+api/services/workspaces/workspaces_test.go @jpmcb @zeucapua
api/utils/validators.go @jpmcb
-cmd/auth/auth.go @jpmcb
+cmd/auth/auth.go @jpmcb @zeucapua
cmd/auth/constants.go @jpmcb
cmd/auth/schema.go @jpmcb
cmd/auth/success.html @jpmcb
cmd/auth/success.html @jpmcb
cmd/bake/bake.go @jpmcb
cmd/bake/bake_test.go @jpmcb
-cmd/docs/docs.go @nickytonline @jpmcb
-cmd/docs/docs_test.go @nickytonline @jpmcb
-cmd/generate/codeowners/codeowners.go @jpmcb @nickytonline @zeucapua
+cmd/docs/docs.go @nickytonline @jpmcb @zeucapua
+cmd/docs/docs_test.go @nickytonline @jpmcb @zeucapua
+cmd/generate/codeowners/codeowners.go @jpmcb @zeucapua
cmd/generate/codeowners/output.go @jpmcb @zeucapua @brandonroberts
-cmd/generate/codeowners/output_test.go @jpmcb @brandonroberts
+cmd/generate/codeowners/output_test.go @jpmcb @brandonroberts @zeucapua
cmd/generate/codeowners/spec.go @jpmcb
cmd/generate/codeowners/traversal.go @jpmcb
-cmd/generate/generate.go @jpmcb
-cmd/insights/contributors.go @jpmcb
+cmd/generate/config/config.go @zeucapua @zeucapua @jpmcb
+cmd/generate/config/output.go @zeucapua @jpmcb @zeucapua
+cmd/generate/config/spec.go @zeucapua @zeucapua @jpmcb
+cmd/generate/generate.go @jpmcb @zeucapua
+cmd/generate/insight/insight.go @jpmcb
+cmd/insights/contributors.go @jpmcb @zeucapua
cmd/insights/insights.go @jpmcb @brandonroberts
-cmd/insights/repositories.go @jpmcb
-cmd/insights/user-contributions.go @jpmcb
+cmd/insights/repositories.go @jpmcb @zeucapua
+cmd/insights/user-contributions.go @jpmcb @zeucapua
cmd/insights/utils.go @jpmcb
cmd/repo-query/repo-query.go @jpmcb
-cmd/root/root.go @jpmcb @brandonroberts @nickytonline
+cmd/root/root.go @jpmcb @brandonroberts @zeucapua
cmd/show/constants.go @jpmcb
cmd/show/contributors.go @jpmcb
cmd/show/dashboard.go @jpmcb
cmd/show/show.go @jpmcb
cmd/show/tui.go @jpmcb
cmd/version/version.go @jpmcb @nickytonline
-docs/pizza.md @nickytonline @jpmcb
-docs/pizza_completion.md @nickytonline @jpmcb
-docs/pizza_completion_bash.md @nickytonline @jpmcb
-docs/pizza_completion_fish.md @nickytonline @jpmcb
-docs/pizza_completion_powershell.md @nickytonline @jpmcb
-docs/pizza_completion_zsh.md @nickytonline @jpmcb
-docs/pizza_generate.md @nickytonline @jpmcb
-docs/pizza_generate_codeowners.md @nickytonline @jpmcb
-docs/pizza_insights.md @nickytonline @jpmcb
-docs/pizza_insights_contributors.md @nickytonline @jpmcb
-docs/pizza_insights_repositories.md @nickytonline @jpmcb
-docs/pizza_insights_user-contributions.md @nickytonline @jpmcb
-docs/pizza_login.md @nickytonline @jpmcb
-docs/pizza_version.md @nickytonline @jpmcb
-go.mod @jpmcb @nickytonline
-go.sum @jpmcb @nickytonline
-justfile @jpmcb @nickytonline
-npm/.gitignore @jpmcb
-npm/package-lock.json @jpmcb @nickytonline @brandonroberts
+docs/pizza.md @jpmcb @zeucapua @nickytonline
+docs/pizza_completion.md @jpmcb @nickytonline @zeucapua
+docs/pizza_completion_bash.md @jpmcb @zeucapua @nickytonline
+docs/pizza_completion_fish.md @jpmcb @nickytonline @zeucapua
+docs/pizza_completion_powershell.md @jpmcb @nickytonline @zeucapua
+docs/pizza_completion_zsh.md @jpmcb @nickytonline @zeucapua
+docs/pizza_generate.md @jpmcb @nickytonline @zeucapua
+docs/pizza_generate_codeowners.md @jpmcb
+docs/pizza_generate_config.md @jpmcb
+docs/pizza_generate_insight.md
+docs/pizza_insights.md @jpmcb @zeucapua @nickytonline
+docs/pizza_insights_contributors.md @jpmcb @zeucapua @nickytonline
+docs/pizza_insights_repositories.md @jpmcb @zeucapua @nickytonline
+docs/pizza_insights_user-contributions.md @jpmcb @nickytonline @zeucapua
+docs/pizza_login.md @jpmcb @nickytonline @zeucapua
+docs/pizza_version.md @jpmcb @nickytonline @zeucapua
+go.mod @jpmcb @nickytonline @zeucapua
+go.sum @jpmcb @zeucapua @zeucapua
+justfile @jpmcb @zeucapua @nickytonline
+npm/.gitignore @jpmcb @zeucapua
+npm/README.md @jpmcb
+npm/package-lock.json @jpmcb @nickytonline @zeucapua
npm/package.json @jpmcb @nickytonline @brandonroberts
pkg/api/client.go @jpmcb
pkg/api/validation.go @jpmcb
-pkg/config/config.go @jpmcb @nickytonline @brandonroberts
-pkg/config/config_test.go @nickytonline @jpmcb @brandonroberts
+pkg/config/config.go @jpmcb @nickytonline @zeucapua
+pkg/config/config_test.go @nickytonline @jpmcb @zeucapua
pkg/config/file.go @jpmcb
-pkg/config/spec.go @jpmcb @brandonroberts
+pkg/config/spec.go @jpmcb @brandonroberts @zeucapua
pkg/constants/flags.go @jpmcb
pkg/logging/constants.go @jpmcb
-pkg/utils/posthog.go @jpmcb
-pkg/utils/root.go @jpmcb
+pkg/utils/posthog.go @jpmcb @zeucapua @zeucapua
+pkg/utils/root.go @jpmcb @zeucapua
pkg/utils/telemetry.go @jpmcb
pkg/utils/version.go @nickytonline @jpmcb
-scripts/generate-docs.sh @nickytonline
-telemetry.go @jpmcb
+scripts/generate-docs.sh @jpmcb @nickytonline
+telemetry.go @jpmcb @zeucapua @zeucapua
diff --git a/README.md b/README.md
index 9f4b80c..b590634 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,35 @@
-
🍕 Pizza CLI 🍕
-
A command line interface and tool for all things OpenSauced!
+
A Go command line interface for managing code ownership and project insights with OpenSauced!
+
+
Watch the overview video 👇
+
+
+
+
+
+
+
+
# 📦 Install
#### Homebrew
@@ -21,29 +44,13 @@ brew install open-sauced/tap/pizza
npm i -g pizza
```
-### Docker
+You can also use `npx` to run one-off commands without installing anything:
```sh
-$ docker run ghcr.io/open-sauced/pizza-cli:latest
-```
-
-For commands that require access to your file system (like `generate codeowners`), ensure
-you pass a volume to the docker container:
-
-```sh
-$ docker run -v /local/path:/container/path ghcr.io/open-sauced/pizza-cli:latest \
- generate codeowners /container/path
-```
-
-For example, to mount your entire home directory (which may include a `.sauced.yaml` file
-alongside the project you want to generate a `CODEOWNERS` file for):
-
-```sh
-$ docker run -v ~/:/app ghcr.io/open-sauced/pizza-cli:latest \
- codeowners /app/workspace/gopherlogs -c /app/.sauced.yaml
+npx pizza@latest generate codeowners .
```
-### Go install
+#### Go install
Using the Go tool-chain, you can install the binary directly:
@@ -51,10 +58,11 @@ Using the Go tool-chain, you can install the binary directly:
$ go install github.com/open-sauced/pizza-cli@latest
```
-Warning! You should have the `GOBIN` env var setup to point to a persistent
-location in your `PATH`. After Go 1.16, this defaults to `GOPATH[0]/bin`.
+> [!WARNING]
+> Warning! You should have the `GOBIN` env var setup to point to a persistent
+> location in your `PATH`. After Go 1.16, this defaults to `GOPATH[0]/bin`.
-### Manual install
+#### Manual install
Download a pre-built artifact from [the GitHub releases](https://github.com/open-sauced/pizza-cli/releases):
@@ -67,7 +75,7 @@ $ chmod +x ~/Downloads/pizza-linux-arm64
$ mv ~/Downloads/pizza-linux-arm64 /usr/local/share/bin/pizza
```
-#### Direct script install
+#### Script install
```sh
curl -fsSL https://raw.githubusercontent.com/open-sauced/pizza-cli/main/install.sh | sh
@@ -91,88 +99,210 @@ your system's `$PATH`.
> ./install.sh
> ```
-#### Manual build
+# 🐳 Docker
-Clone this repository. Then, using the Go tool-chain, you can build a binary:
+Use the container image of the CLI for use in CI/CD or automation:
+```sh
+$ docker run ghcr.io/open-sauced/pizza-cli:latest
```
-$ go build -o build/pizza main.go
+
+For commands that require access to your file system (like `generate codeowners`), ensure
+you pass a volume to the docker container:
+
+```sh
+$ docker run -v /local/path:/container/path ghcr.io/open-sauced/pizza-cli:latest \
+ generate codeowners /container/path
+```
+
+For example, to mount your entire home directory (which may include a `~/.sauced.yaml` file
+alongside the project you want to generate a `CODEOWNERS` file for):
+
+```sh
+$ docker run -v ~/:/app ghcr.io/open-sauced/pizza-cli:latest \
+ codeowners /app/workspace/gopherlogs -c /app/.sauced.yaml
+```
+
+# 🍕 Pizza Action
+
+Use [the Pizza GitHub Action](https://github.com/open-sauced/pizza-action) for running `pizza` operations in GitHub CI/CD,
+like automated `CODEOWNERS` updating and pruning:
+
+```yaml
+jobs:
+ pizza-action:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Pizza Action
+ uses: open-sauced/pizza-action@v2
+ with:
+ # Optional: Whether to commit and create a PR for "CODEOWNER" changes
+ commit-and-pr: "true"
+ # Optional: Title of the PR for review by team
+ pr-title: "chore: update repository codeowners"
```
-Warning! There may be unsupported features, breaking changes, or experimental
-patches on the tip of the repository. Go and build with caution!
+# 📝 Docs
+
+- [Pizza.md](./docs/pizza.md): In depth docs on each command, option, and flag.
+- [OpenSauced.pizza/docs](https://opensauced.pizza/docs/tools/pizza-cli/): Learn
+ how to use the Pizza command line tool and how it works with the rest of the OpenSauced
+ ecosystem.
# ✨ Usage
-### Codeowners generation
+## Codeowners generation
-Use the `codeowners` command to generate an `OWNERS` file or GitHub style `CODEOWNERS` file.
+Use the `codeowners` command to generate a GitHub style `CODEOWNERS` file or a more agnostic `OWNERS` file.
This can be used to granularly define what experts and entities have the
most context and knowledge on certain parts of a codebase.
+It's expected that there's a `.sauced.yaml` config file in the given path or in
+your home directory (as `~/.sauced.yaml`):
+
+```sh
+pizza generate codeowners /path/to/local/git/repo
```
-❯ pizza generate codeowners -h
-WARNING: Proof of concept feature.
+Running this command will iterate the git ref-log to determine who to set as a code
+owner based on the number of lines changed for that file within the given time range.
+The first owner is the entity with the most lines changed. This command uses a `.sauced.yaml` configuration
+to attribute emails in commits with the given entities in the config (like GitHub usernames or teams).
+See [the section on the configuration schema for more details](#-configuration-schema)
+
+### 🚀 New in v1.4.0: Generate Config
+
+The `pizza generate config` command has been added to help you create `.sauced.yaml` configuration files for your projects.
+This command allows you to generate configuration files with various options:
+
+```sh
+pizza generate config /path/to/local/git/repo
+```
-Generates a CODEOWNERS file for a given git repository. This uses a ~/.sauced.yaml
-configuration to attribute emails with given entities.
+This command will iterate the git ref-log and inspect email signatures for commits
+and, in interactive mode, ask you to attribute those users with GitHub handles. Once finished, the resulting
+`.sauced.yaml` file can be used to attribute owners in a `CODEOWNERS` file during `pizza generate codeowners`.
+
+#### Flags:
+
+- `-i, --interactive`: Enter interactive mode to attribute each email manually
+- `-o, --output-path string`: Set the directory for the output file
+- `-h, --help`: Display help for the command
+
+#### Examples:
+
+1. Generate a config file in the current directory:
+ ```sh
+ pizza generate config ./
+ ```
+
+2. Generate a config file interactively:
+ ```sh
+ pizza generate config ./ -i
+ ```
+
+3. Generate a config file from the current directory and place resulting `.sauced.yaml` in a specific output directory:
+ ```sh
+ pizza generate config ./ -o /path/to/directory
+ ```
+
+## OpenSauced Contributor Insight from `CODEOWNERS`
+
+You can create an [OpenSauced Contributor Insight](https://opensauced.pizza/docs/features/contributor-insights/)
+from a local `CODEOWNERS` file:
+
+```
+pizza generate insight /path/to/repo/with/CODEOWNERS/file
+```
-The generated file specifies up to 3 owners for EVERY file in the git tree based on the
-number of lines touched in that specific file over the specified range of time.
+This will parse the `CODEOWNERS` file and create a Contributor Insight on the OpenSauced platform.
+This allows you to track insights and metrics for those codeowners, powered by OpenSauced.
-Usage:
- pizza generate codeowners path/to/repo [flags]
+## Insights
-Flags:
- --owners-style-file Whether to generate an agnostic OWNERS style file.
- -h, --help help for codeowners
- -r, --range int The number of days to lookback (default 90)
+You can get metrics and insights on repositories, contributors, and more:
-Global Flags:
- --beta Shorthand for using the beta OpenSauced API endpoint ("https://beta.api.opensauced.pizza").
- Supersedes the '--endpoint' flag
- -c, --config string The codeowners config (default ".sauced.yaml")
- --disable-telemetry Disable sending telemetry data to OpenSauced
- -e, --endpoint string The API endpoint to send requests to (default "https://api.opensauced.pizza")
- -l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
- -o, --output string The formatting for command output. One of: (table, yaml, csv, json) (default "table")
- --tty-disable Disable log stylization. Suitable for CI/CD and automation
```
+pizza insights [sub-command]
+```
+
+This powerful command lets you compose many metrics and insights together, all
+powered by OpenSauced's API. Use the `--output` flag to output the results as yaml, json, csv, etc.
-### Configuration
+# 🎷 Configuration schema
```yaml
# Configuration for attributing commits with emails to individual entities.
# Used during "pizza generate codeowners".
attribution:
- # Keys can be GitHub usernames. For the "--github-codeowners" style codeowners
- # generation, these keys must be valid GitHub usernames.
+ # Keys can be GitHub usernames.
jpmcb:
- # List of emails associated with the given entity.
+ # List of emails associated with the given GitHub login.
# The commits associated with these emails will be attributed to
- # the entity in this yaml map. Any number of emails may be listed.
+ # this GitHub login in this yaml map. Any number of emails may be listed.
- john@opensauced.pizza
- hello@johncodes.com
- # Entities may also be GitHub teams.
+ # Keys may also be GitHub teams. This is useful for orchestrating multiple
+ # people to a sole GitHub team.
open-sauced/engineering:
- john@opensauced.pizza
- other-user@email.com
- other-user@no-reply.github.com
- # They can also be agnostic names which will land as keys in OWNERS files
+ # Keys can also be agnostic names which will land as keys in "OWNERS" files
+ # when the "--owners-style-file" flag is set.
John McBride
- john@opensauced.pizza
+
+# Used during codeowners generation: if there are no code owners found
+# for a file within the time range, the list of fallback entities
+# will be used
+attribution-fallback:
+ - open-sauced/engineering
+ - some-other-github-login
```
# 🚜 Development
-### 🔨 Requirements
+### Requirements
There are a few things you'll need to get started:
- The [1.22 `go` programming language](https://go.dev/doc/install) toolchain and dev environment (for example, the [VScode Go plugin](https://code.visualstudio.com/docs/languages/go) is very good).
- The [`just` command runner](https://github.com/casey/just) for easy operations
+
+### Building
+
+Clone this repository. Then, using the Go tool-chain, you can build a binary:
+
+```
+go build -o build/pizza main.go
+```
+
+> [!WARNING]
+> There may be unsupported features, breaking changes, or experimental
+> patches on the tip of the repository. Go and build with caution!
+
+There are a number of `just` convinence commands for building with injected buildtime variables
+and targeting other architectures and operating systems.
+
+```
+just build
+```
+```
+just build-all
+```
+
+### Dev operations
+
+There are a number of useful `just` commands that should be used during development:
+- `just lint` will us Golangci-lint to lint the Go code
+- `just clean` removes build artifacts from `build/`
+- `just test` runs the unit and e2e tests
+- `just format` uses goimports to format code
+- ... and many more!
+
+Check `just help` to get a full list of utility dev commands!
diff --git a/cmd/generate/codeowners/codeowners.go b/cmd/generate/codeowners/codeowners.go
index 20491dd..f2ad82b 100644
--- a/cmd/generate/codeowners/codeowners.go
+++ b/cmd/generate/codeowners/codeowners.go
@@ -11,10 +11,6 @@ import (
"github.com/jpmcb/gopherlogs/pkg/colors"
"github.com/spf13/cobra"
- "github.com/open-sauced/pizza-cli/api"
- "github.com/open-sauced/pizza-cli/api/auth"
- "github.com/open-sauced/pizza-cli/api/services/workspaces"
- "github.com/open-sauced/pizza-cli/api/services/workspaces/userlists"
"github.com/open-sauced/pizza-cli/pkg/config"
"github.com/open-sauced/pizza-cli/pkg/constants"
"github.com/open-sauced/pizza-cli/pkg/logging"
@@ -33,9 +29,6 @@ type Options struct {
// the number of days to look back
previousDays int
- // the session token adding codeowners to a workspace contributor list
- token string
-
logger gopherlogs.Logger
tty bool
loglevel int
@@ -43,7 +36,8 @@ type Options struct {
// telemetry for capturing CLI events via PostHog
telemetry *utils.PosthogCliClient
- config *config.Spec
+ config *config.Spec
+ configLoadedPath string
}
const codeownersLongDesc string = `Generates a CODEOWNERS file for a given git repository. The generated file specifies up to 3 owners for EVERY file in the git tree based on the number of lines touched in that specific file over the specified range of time.
@@ -109,7 +103,11 @@ pizza generate codeowners . --config /path/to/.sauced.yaml
opts.telemetry = utils.NewPosthogCliClient(!disableTelem)
configPath, _ := cmd.Flags().GetString("config")
- opts.config, err = config.LoadConfig(configPath)
+ if configPath == "" {
+ configPath = filepath.Join(opts.path, ".sauced.yaml")
+ }
+
+ opts.config, opts.configLoadedPath, err = config.LoadConfig(configPath)
if err != nil {
return err
}
@@ -155,6 +153,7 @@ func run(opts *Options, cmd *cobra.Command) error {
return fmt.Errorf("could not build logger: %w", err)
}
opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Built logger with log level: %d\n", opts.loglevel)
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Loaded config from: %s\n", opts.configLoadedPath)
repo, err := git.PlainOpen(opts.path)
if err != nil {
@@ -194,188 +193,9 @@ func run(opts *Options, cmd *cobra.Command) error {
opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Finished generating file: %s\n", outputPath)
_ = opts.telemetry.CaptureCodeownersGenerate()
- // ignore the interactive prompts for CI/CD environments
- if opts.tty {
- return nil
- }
-
- // 1. Ask if they want to add users to a list
- var input string
- fmt.Print("Do you want to add these codeowners to an OpenSauced Contributor Insight? (y/n): ")
- _, err = fmt.Scanln(&input)
- if err != nil {
- return fmt.Errorf("could not scan input from terminal: %w", err)
- }
-
- switch input {
- case "y", "Y", "yes":
- opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Adding codeowners to contributor insight\n")
- case "n", "N", "no":
- return nil
- default:
- return errors.New("invalid answer. Please enter y or n")
- }
-
- // 2. Check if user is logged in. Log them in if not.
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Initiating log in flow\n")
- authenticator := auth.NewAuthenticator()
- err = authenticator.CheckSession()
- if err != nil {
- opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Log in session invalid: %s\n", err)
- fmt.Print("Do you want to log into OpenSauced? (y/n): ")
- _, err := fmt.Scanln(&input)
- if err != nil {
- return fmt.Errorf("could not scan input from terminal: %w", err)
- }
-
- switch input {
- case "y", "Y", "yes":
- user, err := authenticator.Login()
- if err != nil {
- _ = opts.telemetry.CaptureFailedCodeownersGenerateAuth()
- opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error logging in\n")
- return fmt.Errorf("could not log in: %w", err)
- }
- _ = opts.telemetry.CaptureCodeownersGenerateAuth(user)
- opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Logged in as: %s\n", user)
-
- case "n", "N", "no":
- return nil
-
- default:
- return errors.New("invalid answer. Please enter y or n")
- }
- }
-
- opts.token, err = authenticator.GetSessionToken()
- if err != nil {
- _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
- opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error getting session token\n")
- return fmt.Errorf("could not get session token: %w", err)
- }
-
- listName := filepath.Base(opts.path)
-
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up OpenSauced workspace: Pizza CLI\n")
- workspace, err := findCreatePizzaCliWorkspace(opts)
- if err != nil {
- _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
- opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace: Pizza CLI\n")
- return fmt.Errorf("could not find Pizza CLI workspace: %w", err)
- }
- opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Found workspace: Pizza CLI\n")
-
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up Contributor Insight for local repository: %s\n", listName)
- userList, err := updateCreateLocalWorkspaceUserList(opts, listName, workspace, codeowners)
- if err != nil {
- _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
- opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace Contributor Insight: %s\n", listName)
- return fmt.Errorf("could not find Workspace Contributor Insight: %s - %w", listName, err)
- }
- opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Updated Contributor Insight for local repository: %s\n", listName)
- opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("Access list on OpenSauced:\n%s\n", fmt.Sprintf("https://app.opensauced.pizza/workspaces/%s/contributor-insights/%s", workspace.ID, userList.ID))
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("\nCreate an OpenSauced Contributor Insight to get metrics and insights on these codeowners:\n")
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("$ pizza generate insight " + opts.path + "\n")
_ = opts.telemetry.CaptureCodeownersGenerateContributorInsight()
return nil
}
-
-// findCreatePizzaCliWorkspace finds or creates a "Pizza CLI" workspace
-// for the authenticated user
-func findCreatePizzaCliWorkspace(opts *Options) (*workspaces.DbWorkspace, error) {
- nextPage := true
- page := 1
- apiClient := api.NewClient("https://api.opensauced.pizza")
-
- for nextPage {
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user workspaces page: %d\n", page)
- workspaceResp, _, err := apiClient.WorkspacesService.GetWorkspaces(opts.token, page, 100)
- if err != nil {
- return nil, err
- }
-
- for _, workspace := range workspaceResp.Data {
- if workspace.Name == "Pizza CLI" {
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing workspace named: Pizza CLI\n")
- return &workspace, nil
- }
- }
-
- nextPage = workspaceResp.Meta.HasNextPage
- page++
- }
-
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user workspace: Pizza CLI\n")
- newWorkspace, _, err := apiClient.WorkspacesService.CreateWorkspaceForUser(opts.token, "Pizza CLI", "A workspace for the Pizza CLI", []string{})
- if err != nil {
- return nil, err
- }
-
- return newWorkspace, nil
-}
-
-// updateCreateLocalWorkspaceUserList updates or creates a workspace contributor list
-// for the authenticated user with the given codeowners
-func updateCreateLocalWorkspaceUserList(opts *Options, listName string, workspace *workspaces.DbWorkspace, codeowners FileStats) (*userlists.DbUserList, error) {
- nextPage := true
- page := 1
- apiClient := api.NewClient("https://api.opensauced.pizza")
-
- var targetUserListID string
-
- for nextPage {
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user Workspace Contributor Insight page: %d\n", page)
- userListsResp, _, err := apiClient.WorkspacesService.UserListService.GetUserLists(opts.token, workspace.ID, page, 100)
- if err != nil {
- return nil, err
- }
-
- nextPage = userListsResp.Meta.HasNextPage
- page++
-
- for _, userList := range userListsResp.Data {
- if userList.Name == listName {
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing Workspace Contributor Insight named: %s\n", listName)
- targetUserListID = userList.ID
- nextPage = false
- }
- }
- }
-
- if targetUserListID == "" {
- var err error
-
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user Workspace Contributor List: %s\n", listName)
- createdUserList, _, err := apiClient.WorkspacesService.UserListService.CreateUserListForUser(opts.token, workspace.ID, listName, []string{})
- if err != nil {
- return nil, err
- }
-
- targetUserListID = createdUserList.UserListID
- }
-
- targetUserList, _, err := apiClient.WorkspacesService.UserListService.GetUserList(opts.token, workspace.ID, targetUserListID)
- if err != nil {
- return nil, err
- }
-
- // create a mapping of author logins to empty structs (i.e., a unique set).
- // this de-structures the { filename: author-stats } mapping that originally
- // built the codeowners
- uniqueLogins := make(map[string]struct{})
- for _, codeowner := range codeowners {
- for _, k := range codeowner {
- if k.GitHubAlias != "" {
- uniqueLogins[k.GitHubAlias] = struct{}{}
- }
- }
- }
-
- logins := []string{}
- for login := range uniqueLogins {
- logins = append(logins, login)
- }
-
- opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Updating Contributor Insight with codeowners with GitHub aliases: %v\n", logins)
- userlist, _, err := apiClient.WorkspacesService.UserListService.PatchUserListForUser(opts.token, workspace.ID, targetUserList.ID, targetUserList.Name, logins)
- return userlist, err
-}
diff --git a/cmd/generate/config/config.go b/cmd/generate/config/config.go
index e5e3e19..bc39df4 100644
--- a/cmd/generate/config/config.go
+++ b/cmd/generate/config/config.go
@@ -11,6 +11,9 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/spf13/cobra"
+
+ "github.com/open-sauced/pizza-cli/pkg/constants"
+ "github.com/open-sauced/pizza-cli/pkg/utils"
)
// Options for the config generation command
@@ -26,10 +29,15 @@ type Options struct {
// from global config
ttyDisabled bool
+
+ // telemetry for capturing CLI events via PostHog
+ telemetry *utils.PosthogCliClient
}
-const configLongDesc string = `Generates a ".sauced.yaml" configuration file. The attribution of emails to given entities
-is based on the repository this command is ran in.`
+const configLongDesc string = `Generates a ".sauced.yaml" configuration file for use with the Pizza CLI's codeowners command.
+
+This command analyzes the git history of the current repository to create a mapping
+of email addresses to GitHub usernames. `
func NewConfigCommand() *cobra.Command {
opts := &Options{}
@@ -60,11 +68,17 @@ func NewConfigCommand() *cobra.Command {
},
RunE: func(cmd *cobra.Command, _ []string) error {
+ disableTelem, _ := cmd.Flags().GetBool(constants.FlagNameTelemetry)
+
+ opts.telemetry = utils.NewPosthogCliClient(!disableTelem)
opts.outputPath, _ = cmd.Flags().GetString("output-path")
opts.isInteractive, _ = cmd.Flags().GetBool("interactive")
opts.ttyDisabled, _ = cmd.Flags().GetBool("tty-disable")
- return run(opts)
+ err := run(opts)
+ _ = opts.telemetry.Done()
+
+ return err
},
}
@@ -79,12 +93,14 @@ func run(opts *Options) error {
// Open repo
repo, err := git.PlainOpen(opts.path)
if err != nil {
+ _ = opts.telemetry.CaptureFailedConfigGenerate()
return fmt.Errorf("error opening repo: %w", err)
}
commitIter, err := repo.CommitObjects()
if err != nil {
+ _ = opts.telemetry.CaptureFailedConfigGenerate()
return fmt.Errorf("error opening repo commits: %w", err)
}
@@ -111,11 +127,14 @@ func run(opts *Options) error {
// INTERACTIVE: per unique email, set a name (existing or new or ignore)
if opts.isInteractive && !opts.ttyDisabled {
+ _ = opts.telemetry.CaptureConfigGenerateMode("interactive")
program := tea.NewProgram(initialModel(opts, uniqueEmails))
if _, err := program.Run(); err != nil {
+ _ = opts.telemetry.CaptureFailedConfigGenerate()
return fmt.Errorf("error running interactive mode: %w", err)
}
} else {
+ _ = opts.telemetry.CaptureConfigGenerateMode("automatic")
// generate an output file
// default: `./.sauced.yaml`
// fallback for home directories
@@ -123,15 +142,18 @@ func run(opts *Options) error {
homeDir, _ := os.UserHomeDir()
err := generateOutputFile(filepath.Join(homeDir, ".sauced.yaml"), attributionMap)
if err != nil {
+ _ = opts.telemetry.CaptureFailedConfigGenerate()
return fmt.Errorf("error generating output file: %w", err)
}
} else {
err := generateOutputFile(filepath.Join(opts.outputPath, ".sauced.yaml"), attributionMap)
if err != nil {
+ _ = opts.telemetry.CaptureFailedConfigGenerate()
return fmt.Errorf("error generating output file: %w", err)
}
}
}
+ _ = opts.telemetry.CaptureConfigGenerate()
return nil
}
diff --git a/cmd/generate/generate.go b/cmd/generate/generate.go
index 13d7396..47edbd0 100644
--- a/cmd/generate/generate.go
+++ b/cmd/generate/generate.go
@@ -7,6 +7,7 @@ import (
"github.com/open-sauced/pizza-cli/cmd/generate/codeowners"
"github.com/open-sauced/pizza-cli/cmd/generate/config"
+ "github.com/open-sauced/pizza-cli/cmd/generate/insight"
)
const generateLongDesc string = `The 'generate' command provides tools to automate the creation of important project documentation and derive insights from your codebase.`
@@ -28,6 +29,7 @@ func NewGenerateCommand() *cobra.Command {
cmd.AddCommand(codeowners.NewCodeownersCommand())
cmd.AddCommand(config.NewConfigCommand())
+ cmd.AddCommand(insight.NewGenerateInsightCommand())
return cmd
}
diff --git a/cmd/generate/insight/insight.go b/cmd/generate/insight/insight.go
new file mode 100644
index 0000000..5769316
--- /dev/null
+++ b/cmd/generate/insight/insight.go
@@ -0,0 +1,332 @@
+package insight
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/jpmcb/gopherlogs"
+ "github.com/jpmcb/gopherlogs/pkg/colors"
+ "github.com/spf13/cobra"
+
+ "github.com/open-sauced/pizza-cli/api"
+ "github.com/open-sauced/pizza-cli/api/auth"
+ "github.com/open-sauced/pizza-cli/api/services/workspaces"
+ "github.com/open-sauced/pizza-cli/api/services/workspaces/userlists"
+ "github.com/open-sauced/pizza-cli/pkg/constants"
+ "github.com/open-sauced/pizza-cli/pkg/logging"
+ "github.com/open-sauced/pizza-cli/pkg/utils"
+)
+
+// Options for the codeowners generation command
+type Options struct {
+ // the path to the git repository on disk to generate a codeowners file for
+ path string
+
+ logger gopherlogs.Logger
+ tty bool
+ loglevel int
+
+ token string
+
+ // telemetry for capturing CLI events via PostHog
+ telemetry *utils.PosthogCliClient
+}
+
+const insightLongDesc string = `Generate an OpenSauced Contributor Insight based on GitHub logins in a CODEOWNERS file
+to get metrics and insights on those users.
+
+The provided path must be a local git repo with a valid CODEOWNERS file and GitHub "@login"
+for each codeowner.
+
+After logging in, the generated Contributor Insight on OpenSauced will have insights on
+active contributors, contributon velocity, and more.`
+
+const insightExamples string = ` # Use CODEOWNERS file in explicit directory
+ $ pizza generate insight /path/to/repo
+
+ # Use CODEOWNERS file in local directory
+ $ pizza generate insight .`
+
+func NewGenerateInsightCommand() *cobra.Command {
+ opts := &Options{}
+
+ cmd := &cobra.Command{
+ Use: "insight path/to/repo/with/CODEOWNERS/file [flags]",
+ Short: "Generate an OpenSauced Contributor Insight based on GitHub logins in a CODEOWNERS file",
+ Long: insightLongDesc,
+ Example: insightExamples,
+ Args: func(_ *cobra.Command, args []string) error {
+ if len(args) != 1 {
+ return errors.New("you must provide exactly one argument: the path to a repository with a codeowners file")
+ }
+
+ path := args[0]
+
+ // Validate that the path is a real path on disk and accessible by the user
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ return err
+ }
+
+ if _, err := os.Stat(absPath); os.IsNotExist(err) {
+ return fmt.Errorf("the provided path does not exist: %w", err)
+ }
+
+ opts.path = absPath
+ return nil
+ },
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ var err error
+
+ disableTelem, _ := cmd.Flags().GetBool(constants.FlagNameTelemetry)
+
+ opts.telemetry = utils.NewPosthogCliClient(!disableTelem)
+ opts.tty, _ = cmd.Flags().GetBool("tty-disable")
+
+ loglevelS, _ := cmd.Flags().GetString("log-level")
+
+ switch loglevelS {
+ case "error":
+ opts.loglevel = logging.LogError
+ case "warn":
+ opts.loglevel = logging.LogWarn
+ case "info":
+ opts.loglevel = logging.LogInfo
+ case "debug":
+ opts.loglevel = logging.LogDebug
+ }
+
+ err = run(opts, cmd)
+
+ _ = opts.telemetry.Done()
+
+ return err
+ },
+ }
+
+ return cmd
+}
+
+func run(opts *Options, _ *cobra.Command) error {
+ var err error
+ opts.logger, err = gopherlogs.NewLogger(
+ gopherlogs.WithLogVerbosity(opts.loglevel),
+ gopherlogs.WithTty(!opts.tty),
+ )
+ if err != nil {
+ return fmt.Errorf("could not build logger: %w", err)
+ }
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Built logger with log level: %d\n", opts.loglevel)
+
+ codeowners, err := getUniqueCodeowners(filepath.Join(opts.path, "CODEOWNERS"))
+ if err != nil {
+ return fmt.Errorf("could not get codeowners from file: %s - %w", opts.path, err)
+ }
+
+ // 1. Ask if they want to add users to a list
+ var input string
+ fmt.Print("Do you want to add these codeowners to an OpenSauced Contributor Insight? (y/n): ")
+ _, err = fmt.Scanln(&input)
+ if err != nil {
+ return fmt.Errorf("could not scan input from terminal: %w", err)
+ }
+
+ switch input {
+ case "y", "Y", "yes":
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Adding codeowners to Contributor Insight\n")
+ case "n", "N", "no":
+ return nil
+ default:
+ return errors.New("invalid answer. Please enter y or n")
+ }
+
+ // 2. Check if user is logged in. Log them in if not.
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Initiating log in flow\n")
+ authenticator := auth.NewAuthenticator()
+ err = authenticator.CheckSession()
+ if err != nil {
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Log in session invalid: %s\n", err)
+ fmt.Print("Do you want to log into OpenSauced? (y/n): ")
+ _, err := fmt.Scanln(&input)
+ if err != nil {
+ return fmt.Errorf("could not scan input from terminal: %w", err)
+ }
+
+ switch input {
+ case "y", "Y", "yes":
+ user, err := authenticator.Login()
+ if err != nil {
+ _ = opts.telemetry.CaptureFailedCodeownersGenerateAuth()
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error logging in\n")
+ return fmt.Errorf("could not log in: %w", err)
+ }
+ _ = opts.telemetry.CaptureCodeownersGenerateAuth(user)
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgGreen).Infof("Logged in as: %s\n", user)
+
+ case "n", "N", "no":
+ return nil
+
+ default:
+ return errors.New("invalid answer. Please enter y or n")
+ }
+ }
+
+ opts.token, err = authenticator.GetSessionToken()
+ if err != nil {
+ _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error getting session token\n")
+ return fmt.Errorf("could not get session token: %w", err)
+ }
+
+ listName := filepath.Base(opts.path)
+
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up OpenSauced workspace: Pizza CLI\n")
+ workspace, err := findCreatePizzaCliWorkspace(opts)
+ if err != nil {
+ _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace: Pizza CLI\n")
+ return fmt.Errorf("could not find Pizza CLI workspace: %w", err)
+ }
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Found workspace: Pizza CLI\n")
+
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Looking up Contributor Insight for local repository: %s\n", listName)
+ userList, err := updateCreateLocalWorkspaceUserList(opts, listName, workspace, codeowners)
+ if err != nil {
+ _ = opts.telemetry.CaptureFailedCodeownersGenerateContributorInsight()
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgRed).Infof("Error finding Workspace Contributor Insight: %s\n", listName)
+ return fmt.Errorf("could not find Workspace Contributor Insight: %s - %w", listName, err)
+ }
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgGreen).Infof("Updated Contributor Insight for local repository: %s\n", listName)
+ opts.logger.V(logging.LogInfo).Style(0, colors.FgCyan).Infof("\nAccess Contributor Insight on OpenSauced:\n%s\n", fmt.Sprintf("https://app.opensauced.pizza/workspaces/%s/contributor-insights/%s", workspace.ID, userList.ID))
+ _ = opts.telemetry.CaptureCodeownersGenerateContributorInsight()
+
+ return nil
+}
+
+func getUniqueCodeowners(path string) ([]string, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, fmt.Errorf("error opening file: %w", err)
+ }
+ defer file.Close()
+
+ // Create a map to store unique uniqueLogins
+ uniqueLogins := make(map[string]struct{})
+
+ // Create a regular expression to match GitHub usernames
+ re := regexp.MustCompile(`@(\w+)`)
+
+ // Read the file line by line
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ // Find all matches in the line
+ matches := re.FindAllStringSubmatch(line, -1)
+
+ // Add each match to the map
+ for _, match := range matches {
+ uniqueLogins[match[1]] = struct{}{}
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading file: %w", err)
+ }
+
+ logins := []string{}
+ for login := range uniqueLogins {
+ logins = append(logins, login)
+ }
+
+ fmt.Printf("%v\n", logins)
+ return logins, nil
+}
+
+// findCreatePizzaCliWorkspace finds or creates a "Pizza CLI" workspace
+// for the authenticated user
+func findCreatePizzaCliWorkspace(opts *Options) (*workspaces.DbWorkspace, error) {
+ nextPage := true
+ page := 1
+ apiClient := api.NewClient("https://api.opensauced.pizza")
+
+ for nextPage {
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user workspaces page: %d\n", page)
+ workspaceResp, _, err := apiClient.WorkspacesService.GetWorkspaces(opts.token, page, 100)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, workspace := range workspaceResp.Data {
+ if workspace.Name == "Pizza CLI" {
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing workspace named: Pizza CLI\n")
+ return &workspace, nil
+ }
+ }
+
+ nextPage = workspaceResp.Meta.HasNextPage
+ page++
+ }
+
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user workspace: Pizza CLI\n")
+ newWorkspace, _, err := apiClient.WorkspacesService.CreateWorkspaceForUser(opts.token, "Pizza CLI", "A workspace for the Pizza CLI", []string{})
+ if err != nil {
+ return nil, err
+ }
+
+ return newWorkspace, nil
+}
+
+// updateCreateLocalWorkspaceUserList updates or creates a workspace contributor list
+// for the authenticated user with the given codeowners
+func updateCreateLocalWorkspaceUserList(opts *Options, listName string, workspace *workspaces.DbWorkspace, logins []string) (*userlists.DbUserList, error) {
+ nextPage := true
+ page := 1
+ apiClient := api.NewClient("https://api.opensauced.pizza")
+
+ var targetUserListID string
+
+ for nextPage {
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Query user Workspace Contributor Insight page: %d\n", page)
+ userListsResp, _, err := apiClient.WorkspacesService.UserListService.GetUserLists(opts.token, workspace.ID, page, 100)
+ if err != nil {
+ return nil, err
+ }
+
+ nextPage = userListsResp.Meta.HasNextPage
+ page++
+
+ for _, userList := range userListsResp.Data {
+ if userList.Name == listName {
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Found existing Workspace Contributor Insight named: %s\n", listName)
+ targetUserListID = userList.ID
+ nextPage = false
+ }
+ }
+ }
+
+ if targetUserListID == "" {
+ var err error
+
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Creating new user Workspace Contributor List: %s\n", listName)
+ createdUserList, _, err := apiClient.WorkspacesService.UserListService.CreateUserListForUser(opts.token, workspace.ID, listName, []string{})
+ if err != nil {
+ return nil, err
+ }
+
+ targetUserListID = createdUserList.UserListID
+ }
+
+ targetUserList, _, err := apiClient.WorkspacesService.UserListService.GetUserList(opts.token, workspace.ID, targetUserListID)
+ if err != nil {
+ return nil, err
+ }
+
+ opts.logger.V(logging.LogDebug).Style(0, colors.FgBlue).Infof("Updating Contributor Insight with codeowners with GitHub aliases: %v\n", logins)
+ userlist, _, err := apiClient.WorkspacesService.UserListService.PatchUserListForUser(opts.token, workspace.ID, targetUserList.ID, targetUserList.Name, logins)
+ return userlist, err
+}
diff --git a/cmd/root/root.go b/cmd/root/root.go
index 7b35e4a..bbf81ae 100644
--- a/cmd/root/root.go
+++ b/cmd/root/root.go
@@ -36,7 +36,7 @@ func NewRootCommand() (*cobra.Command, error) {
cmd.PersistentFlags().StringP(constants.FlagNameEndpoint, "e", constants.EndpointProd, "The API endpoint to send requests to")
cmd.PersistentFlags().Bool(constants.FlagNameBeta, false, fmt.Sprintf("Shorthand for using the beta OpenSauced API endpoint (\"%s\"). Supersedes the '--%s' flag", constants.EndpointBeta, constants.FlagNameEndpoint))
cmd.PersistentFlags().Bool(constants.FlagNameTelemetry, false, "Disable sending telemetry data to OpenSauced")
- cmd.PersistentFlags().StringP("config", "c", "~/.sauced.yaml", "The codeowners config")
+ cmd.PersistentFlags().StringP("config", "c", "", "The codeowners config")
cmd.PersistentFlags().StringP("log-level", "l", "info", "The logging level. Options: error, warn, info, debug")
cmd.PersistentFlags().Bool("tty-disable", false, "Disable log stylization. Suitable for CI/CD and automation")
diff --git a/docs/pizza.md b/docs/pizza.md
index 58ea659..dfd6ca4 100644
--- a/docs/pizza.md
+++ b/docs/pizza.md
@@ -13,7 +13,7 @@ pizza [flags]
### Options
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-h, --help help for pizza
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
diff --git a/docs/pizza_completion.md b/docs/pizza_completion.md
index 1506e1b..c3a9471 100644
--- a/docs/pizza_completion.md
+++ b/docs/pizza_completion.md
@@ -17,7 +17,7 @@ See each sub-command's help for details on how to use the generated script.
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_completion_bash.md b/docs/pizza_completion_bash.md
index 3a32c97..ea8aae4 100644
--- a/docs/pizza_completion_bash.md
+++ b/docs/pizza_completion_bash.md
@@ -40,7 +40,7 @@ pizza completion bash
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_completion_fish.md b/docs/pizza_completion_fish.md
index 734d1e0..a8d278a 100644
--- a/docs/pizza_completion_fish.md
+++ b/docs/pizza_completion_fish.md
@@ -31,7 +31,7 @@ pizza completion fish [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_completion_powershell.md b/docs/pizza_completion_powershell.md
index 06b2036..4de607a 100644
--- a/docs/pizza_completion_powershell.md
+++ b/docs/pizza_completion_powershell.md
@@ -28,7 +28,7 @@ pizza completion powershell [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_completion_zsh.md b/docs/pizza_completion_zsh.md
index 5e45162..ccc8522 100644
--- a/docs/pizza_completion_zsh.md
+++ b/docs/pizza_completion_zsh.md
@@ -42,7 +42,7 @@ pizza completion zsh [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_generate.md b/docs/pizza_generate.md
index 61b0e8e..49c37ca 100644
--- a/docs/pizza_generate.md
+++ b/docs/pizza_generate.md
@@ -19,7 +19,7 @@ pizza generate [subcommand] [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
@@ -30,4 +30,5 @@ pizza generate [subcommand] [flags]
* [pizza](pizza.md) - OpenSauced CLI
* [pizza generate codeowners](pizza_generate_codeowners.md) - Generate a CODEOWNERS file for a GitHub repository using a "~/.sauced.yaml" config
* [pizza generate config](pizza_generate_config.md) - Generates a ".sauced.yaml" config based on the current repository
+* [pizza generate insight](pizza_generate_insight.md) - Generate an OpenSauced Contributor Insight based on GitHub logins in a CODEOWNERS file
diff --git a/docs/pizza_generate_codeowners.md b/docs/pizza_generate_codeowners.md
index dcbbfd0..fc69c5e 100644
--- a/docs/pizza_generate_codeowners.md
+++ b/docs/pizza_generate_codeowners.md
@@ -52,7 +52,7 @@ pizza generate codeowners . --config /path/to/.sauced.yaml
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_generate_config.md b/docs/pizza_generate_config.md
index 0366cad..bc89a18 100644
--- a/docs/pizza_generate_config.md
+++ b/docs/pizza_generate_config.md
@@ -4,8 +4,10 @@ Generates a ".sauced.yaml" config based on the current repository
### Synopsis
-Generates a ".sauced.yaml" configuration file. The attribution of emails to given entities
-is based on the repository this command is ran in.
+Generates a ".sauced.yaml" configuration file for use with the Pizza CLI's codeowners command.
+
+This command analyzes the git history of the current repository to create a mapping
+of email addresses to GitHub usernames.
```
pizza generate config path/to/repo [flags]
@@ -22,7 +24,7 @@ pizza generate config path/to/repo [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_generate_insight.md b/docs/pizza_generate_insight.md
new file mode 100644
index 0000000..628c1a3
--- /dev/null
+++ b/docs/pizza_generate_insight.md
@@ -0,0 +1,48 @@
+## pizza generate insight
+
+Generate an OpenSauced Contributor Insight based on GitHub logins in a CODEOWNERS file
+
+### Synopsis
+
+Generate an OpenSauced Contributor Insight based on GitHub logins in a CODEOWNERS file
+to get metrics and insights on those users.
+
+The provided path must be a local git repo with a valid CODEOWNERS file and GitHub "@login"
+for each codeowner.
+
+After logging in, the generated Contributor Insight on OpenSauced will have insights on
+active contributors, contributon velocity, and more.
+
+```
+pizza generate insight path/to/repo/with/CODEOWNERS/file [flags]
+```
+
+### Examples
+
+```
+ # Use CODEOWNERS file in explicit directory
+ $ pizza generate insight /path/to/repo
+
+ # Use CODEOWNERS file in local directory
+ $ pizza generate insight .
+```
+
+### Options
+
+```
+ -h, --help help for insight
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config string The codeowners config
+ --disable-telemetry Disable sending telemetry data to OpenSauced
+ -l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
+ --tty-disable Disable log stylization. Suitable for CI/CD and automation
+```
+
+### SEE ALSO
+
+* [pizza generate](pizza_generate.md) - Generates documentation and insights from your codebase
+
diff --git a/docs/pizza_insights.md b/docs/pizza_insights.md
index 7933480..27007db 100644
--- a/docs/pizza_insights.md
+++ b/docs/pizza_insights.md
@@ -20,7 +20,7 @@ pizza insights [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_insights_contributors.md b/docs/pizza_insights_contributors.md
index 6c9d868..c228baa 100644
--- a/docs/pizza_insights_contributors.md
+++ b/docs/pizza_insights_contributors.md
@@ -21,7 +21,7 @@ pizza insights contributors url... [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
-o, --output string The formatting for command output. One of: (table, yaml, csv, json) (default "table")
diff --git a/docs/pizza_insights_repositories.md b/docs/pizza_insights_repositories.md
index 9754648..f7d5036 100644
--- a/docs/pizza_insights_repositories.md
+++ b/docs/pizza_insights_repositories.md
@@ -21,7 +21,7 @@ pizza insights repositories url... [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
-o, --output string The formatting for command output. One of: (table, yaml, csv, json) (default "table")
diff --git a/docs/pizza_insights_user-contributions.md b/docs/pizza_insights_user-contributions.md
index 8f07c9d..e64060b 100644
--- a/docs/pizza_insights_user-contributions.md
+++ b/docs/pizza_insights_user-contributions.md
@@ -23,7 +23,7 @@ pizza insights user-contributions url... [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
-o, --output string The formatting for command output. One of: (table, yaml, csv, json) (default "table")
diff --git a/docs/pizza_login.md b/docs/pizza_login.md
index 54ead8f..d1c5965 100644
--- a/docs/pizza_login.md
+++ b/docs/pizza_login.md
@@ -22,7 +22,7 @@ pizza login [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/docs/pizza_version.md b/docs/pizza_version.md
index ce6c725..51efa17 100644
--- a/docs/pizza_version.md
+++ b/docs/pizza_version.md
@@ -15,7 +15,7 @@ pizza version [flags]
### Options inherited from parent commands
```
- -c, --config string The codeowners config (default "~/.sauced.yaml")
+ -c, --config string The codeowners config
--disable-telemetry Disable sending telemetry data to OpenSauced
-l, --log-level string The logging level. Options: error, warn, info, debug (default "info")
--tty-disable Disable log stylization. Suitable for CI/CD and automation
diff --git a/justfile b/justfile
index a5b8a10..df7a237 100644
--- a/justfile
+++ b/justfile
@@ -11,14 +11,15 @@ build:
echo "Building for local arch"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza
# Builds and installs the go binary for the local architecture. WARNING: requires sudo access
@@ -40,17 +41,18 @@ build-darwin-amd64:
echo "Building darwin amd64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="darwin"
export GOARCH="amd64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds for Darwin linux (i.e., MacOS) on arm64 architecture (i.e. Apple silicon)
@@ -60,17 +62,18 @@ build-darwin-arm64:
echo "Building darwin arm64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="darwin"
export GOARCH="arm64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds for agnostic Linux on amd64 architecture
@@ -80,17 +83,18 @@ build-linux-amd64:
echo "Building linux amd64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="linux"
export GOARCH="amd64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds for agnostic Linux on arm64 architecture
@@ -100,17 +104,18 @@ build-linux-arm64:
echo "Building linux arm64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="linux"
export GOARCH="arm64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds for Windows on amd64 architecture
@@ -120,17 +125,18 @@ build-windows-amd64:
echo "Building windows amd64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="windows"
export GOARCH="amd64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds for Windows on arm64 architecture
@@ -140,25 +146,34 @@ build-windows-arm64:
echo "Building windows arm64"
export VERSION="${RELEASE_TAG_VERSION:-dev}"
- export DATETIME=$(date -u +"%Y-%m-%d %H:%M:%S")
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
export CGO_ENABLED=0
export GOOS="windows"
export GOARCH="arm64"
go build \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=$(git rev-parse HEAD)'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}'" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Version=${VERSION}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Sha=${SHA}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.Datetime=${DATETIME}' \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/pizza-${GOOS}-${GOARCH}
# Builds the Docker container and tags it as "dev"
build-container:
+ #!/usr/bin/env sh
+
+ echo "Building container"
+
+ export VERSION="${RELEASE_TAG_VERSION:-dev}"
+ export DATETIME=$(date -u +"%Y-%m-%d-%H:%M:%S")
+ export SHA=$(git rev-parse HEAD)
+
docker build \
- --build-arg VERSION="$(git describe --tags --always)" \
- --build-arg SHA="$(git rev-parse HEAD)" \
- --build-arg DATETIME="$(date -u +'%Y-%m-%d %H:%M:%S')" \
+ --build-arg VERSION="${VERSION}" \
+ --build-arg SHA="${SHA}" \
+ --build-arg DATETIME="${DATETIME}" \
--build-arg POSTHOG_PUBLIC_API_KEY="${POSTHOG_PUBLIC_API_KEY}" \
-t pizza:dev .
@@ -211,8 +226,8 @@ bootstrap-telemetry:
go build \
-tags telemetry \
- -ldflags="-s -w" \
- -ldflags="-X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
+ -ldflags="-s -w \
+ -X 'github.com/open-sauced/pizza-cli/pkg/utils.writeOnlyPublicPosthogKey=${POSTHOG_PUBLIC_API_KEY}'" \
-o build/telemetry-oneshot \
telemetry.go
diff --git a/npm/README.md b/npm/README.md
index 2d1a8e9..8f4bd4d 100644
--- a/npm/README.md
+++ b/npm/README.md
@@ -1,75 +1,187 @@
-
+
🍕 Pizza CLI 🍕
-
A Go command line interface for all things OpenSauced!
+
A Go command line interface for managing code ownership and project insights with OpenSauced!
-
+
+
+---
+# 📦 Install
+
+#### Homebrew
+
+```sh
+brew install open-sauced/tap/pizza
```
-❯ pizza
-A command line utility for insights, metrics, and all things OpenSauced
+#### NPM
-Usage:
- pizza [flags]
+```sh
+npm i -g pizza
+```
-Available Commands:
- bake Use a pizza-oven to source git commits into OpenSauced
- completion Generate the autocompletion script for the specified shell
- help Help about any command
- login Log into the CLI application via GitHub
- repo-query Ask questions about a GitHub repository
+You can also use `npx` to run one-off commands without installing anything:
-Flags:
- -h, --help help for pizza
+```sh
+npx pizza@latest generate codeowners .
+```
-Use "pizza [command] --help" for more information about a command.
+# 🍕 Pizza Action
+
+Use [the Pizza GitHub Action](https://github.com/open-sauced/pizza-action) for running `pizza` operations in GitHub CI/CD,
+like automated `CODEOWNERS` updating and pruning:
+
+```yaml
+jobs:
+ pizza-action:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Pizza Action
+ uses: open-sauced/pizza-action@v2
+ with:
+ # Optional: Whether to commit and create a PR for "CODEOWNER" changes
+ commit-and-pr: "true"
+ # Optional: Title of the PR for review by team
+ pr-title: "chore: update repository codeowners"
```
----
+# 📝 Docs
-### 📦 Install
+- [Pizza.md](./docs/pizza.md): In depth docs on each command, option, and flag.
+- [OpenSauced.pizza/docs](https://opensauced.pizza/docs/tools/pizza-cli/): Learn
+ how to use the Pizza command line tool and how it works with the rest of the OpenSauced
+ ecosystem.
-There are several methods for downloading and installing the `pizza` CLI:
+# ✨ Usage
-#### Homebrew
+## Codeowners generation
+
+Use the `codeowners` command to generate a GitHub style `CODEOWNERS` file or a more agnostic `OWNERS` file.
+This can be used to granularly define what experts and entities have the
+most context and knowledge on certain parts of a codebase.
+
+It's expected that there's a `.sauced.yaml` config file in the given path or in
+your home directory (as `~/.sauced.yaml`):
```sh
-brew install open-sauced/tap/pizza
+pizza generate codeowners /path/to/local/git/repo
```
-#### NPM
+Running this command will iterate the git ref-log to determine who to set as a code
+owner based on the number of lines changed for that file within the given time range.
+The first owner is the entity with the most lines changed. This command uses a `.sauced.yaml` configuration
+to attribute emails in commits with the given entities in the config (like GitHub usernames or teams).
+See [the section on the configuration schema for more details](#-configuration-schema)
+
+### 🚀 New in v1.4.0: Generate Config
+
+The `pizza generate config` command has been added to help you create `.sauced.yaml` configuration files for your projects.
+This command allows you to generate configuration files with various options:
```sh
-npm i -g pizza
+pizza generate config /path/to/local/git/repo
```
-#### Direct script install
+This command will iterate the git ref-log and inspect email signatures for commits
+and, in interactive mode, ask you to attribute those users with GitHub handles. Once finished, the resulting
+`.sauced.yaml` file can be used to attribute owners in a `CODEOWNERS` file during `pizza generate codeowners`.
+
+#### Flags:
+
+- `-i, --interactive`: Enter interactive mode to attribute each email manually
+- `-o, --output-path string`: Set the directory for the output file
+- `-h, --help`: Display help for the command
+
+#### Examples:
+
+1. Generate a config file in the current directory:
+ ```sh
+ pizza generate config ./
+ ```
+
+2. Generate a config file interactively:
+ ```sh
+ pizza generate config ./ -i
+ ```
+
+3. Generate a config file from the current directory and place resulting `.sauced.yaml` in a specific output directory:
+ ```sh
+ pizza generate config ./ -o /path/to/directory
+ ```
+
+## OpenSauced Contributor Insight from `CODEOWNERS`
+
+You can create an [OpenSauced Contributor Insight](https://opensauced.pizza/docs/features/contributor-insights/)
+from a local `CODEOWNERS` file:
-```sh
-curl -fsSL https://raw.githubusercontent.com/open-sauced/pizza-cli/main/install.sh | sh
```
+pizza generate insight /path/to/repo/with/CODEOWNERS/file
+```
+
+This will parse the `CODEOWNERS` file and create a Contributor Insight on the OpenSauced platform.
+This allows you to track insights and metrics for those codeowners, powered by OpenSauced.
-This is a convenience script that can be downloaded from GitHub directly and
-piped into `sh` for conveniently downloading the latest GitHub release of the
-`pizza` CLI.
+## Insights
-Once download is completed, you can move the binary to a convenient location in
-your system's `$PATH`.
\ No newline at end of file
+You can get metrics and insights on repositories, contributors, and more:
+
+```
+pizza insights [sub-command]
+```
+
+This powerful command lets you compose many metrics and insights together, all
+powered by OpenSauced's API. Use the `--output` flag to output the results as yaml, json, csv, etc.
+
+# 🎷 Configuration schema
+
+```yaml
+# Configuration for attributing commits with emails to individual entities.
+# Used during "pizza generate codeowners".
+attribution:
+
+ # Keys can be GitHub usernames.
+ jpmcb:
+
+ # List of emails associated with the given GitHub login.
+ # The commits associated with these emails will be attributed to
+ # this GitHub login in this yaml map. Any number of emails may be listed.
+ - john@opensauced.pizza
+ - hello@johncodes.com
+
+ # Keys may also be GitHub teams. This is useful for orchestrating multiple
+ # people to a sole GitHub team.
+ open-sauced/engineering:
+ - john@opensauced.pizza
+ - other-user@email.com
+ - other-user@no-reply.github.com
+
+ # Keys can also be agnostic names which will land as keys in "OWNERS" files
+ # when the "--owners-style-file" flag is set.
+ John McBride
+ - john@opensauced.pizza
+
+# Used during codeowners generation: if there are no code owners found
+# for a file within the time range, the list of fallback entities
+# will be used
+attribution-fallback:
+ - open-sauced/engineering
+ - some-other-github-login
+```
diff --git a/npm/package-lock.json b/npm/package-lock.json
index 1549606..947def7 100644
--- a/npm/package-lock.json
+++ b/npm/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pizza",
- "version": "1.4.0",
+ "version": "2.0.0-beta.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pizza",
- "version": "1.4.0",
+ "version": "2.0.0-beta.1",
"hasInstallScript": true,
"license": "MIT",
"bin": {
diff --git a/npm/package.json b/npm/package.json
index 28f6e41..6b740b1 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -1,7 +1,7 @@
{
"name": "pizza",
- "version": "1.4.0",
- "description": "A command line utility for insights, metrics, and all things OpenSauced",
+ "version": "2.0.0-beta.1",
+ "description": "A command line utility for insights, metrics, and generating CODEOWNERS documentation for your open source projects",
"repository": "https://github.com/open-sauced/pizza-cli",
"license": "MIT",
"bin": "./bin/runner.js",
diff --git a/pkg/config/config.go b/pkg/config/config.go
index ed722c5..98ec6f6 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -9,55 +9,57 @@ import (
"gopkg.in/yaml.v3"
)
-// LoadConfig loads a configuration file at a given path. It attempts to load
-// the default location of a ".sauced.yaml" in the current working directory if an
-// empty path is provided. If none is found, it tries to load
-// "~/.sauced.yaml" from the fallback path, which is the user's home directory.
-func LoadConfig(path string) (*Spec, error) {
- println("Config path loading from -c flag", path)
+// LoadConfig loads a configuration file at a given path.
+// If the provided path does not exist or doesn't contain a ".sauced.yaml" file,
+// "~/.sauced.yaml" from the fallback path, which is the user's home directory, is used.
+//
+// This function returns the config Spec, the location the spec was loaded from, and an error
+func LoadConfig(path string) (*Spec, string, error) {
+ givenPathSpec, givenLoadedPath, givenPathErr := loadSpecAtPath(path)
+ if givenPathErr == nil {
+ return givenPathSpec, givenLoadedPath, nil
+ }
+
+ homePathSpec, homeLoadedPath, homePathErr := loadSpecAtHome()
+ if homePathErr == nil {
+ return homePathSpec, homeLoadedPath, nil
+ }
+ return nil, "", fmt.Errorf("could not load config at given path: %w - could not load config at home: %w", givenPathErr, homePathErr)
+}
+
+func loadSpecAtPath(path string) (*Spec, string, error) {
config := &Spec{}
absPath, err := filepath.Abs(path)
if err != nil {
- return nil, fmt.Errorf("error resolving absolute path: %w", err)
+ return nil, "", fmt.Errorf("error resolving absolute path: %s - %w", path, err)
}
data, err := os.ReadFile(absPath)
if err != nil {
- // If the file does not exist, check if the fallback path exists
- if os.IsNotExist(err) {
- // load the default file path under the user's home dir
- usr, err := user.Current()
-
- if err != nil {
- return nil, fmt.Errorf("could not get user home directory: %w", err)
- }
-
- homeDirPathConfig, err := filepath.Abs(filepath.Join(usr.HomeDir, ".sauced.yaml"))
-
- if err != nil {
- return nil, fmt.Errorf("error home directory absolute path: %w", err)
- }
-
- _, err = os.Stat(homeDirPathConfig)
- if err != nil {
- return nil, fmt.Errorf("error reading config file from %s", homeDirPathConfig)
- }
-
- data, err = os.ReadFile(homeDirPathConfig)
- if err != nil {
- return nil, fmt.Errorf("error reading config file from %s or %s", absPath, homeDirPathConfig)
- }
- } else {
- return nil, fmt.Errorf("error reading config file: %w", err)
- }
+ return nil, "", fmt.Errorf("error reading config file from given absolute path: %s - %w", absPath, err)
}
err = yaml.Unmarshal(data, config)
if err != nil {
- return nil, fmt.Errorf("error unmarshaling config: %w", err)
+ return nil, "", fmt.Errorf("error unmarshaling config at: %s - %w", absPath, err)
+ }
+
+ return config, absPath, nil
+}
+
+func loadSpecAtHome() (*Spec, string, error) {
+ usr, err := user.Current()
+ if err != nil {
+ return nil, "", fmt.Errorf("could not get user home directory: %w", err)
+ }
+
+ path := filepath.Join(usr.HomeDir, ".sauced.yaml")
+ conf, loadedPath, err := loadSpecAtPath(path)
+ if err != nil {
+ return nil, "", fmt.Errorf("could not load spec at home: %s - %w", path, err)
}
- return config, nil
+ return conf, loadedPath, nil
}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index c0da2e6..8611f5e 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -30,7 +30,7 @@ attribution:
require.NoError(t, os.WriteFile(configFilePath, []byte(fileContents), 0600))
- config, err := LoadConfig(configFilePath)
+ config, _, err := LoadConfig(configFilePath)
require.NoError(t, err)
assert.NotNil(t, config)
@@ -47,7 +47,7 @@ attribution:
tmpDir := t.TempDir()
nonExistentPath := filepath.Join(tmpDir, ".sauced.yaml")
- config, err := LoadConfig(nonExistentPath)
+ config, _, err := LoadConfig(nonExistentPath)
require.Error(t, err)
assert.Nil(t, config)
})
@@ -78,7 +78,7 @@ attribution:
_, err := os.ReadFile(fallbackPath)
require.NoError(t, err)
- config, err := LoadConfig(fallbackPath)
+ config, _, err := LoadConfig(fallbackPath)
require.NoError(t, err)
assert.NotNil(t, config)
diff --git a/pkg/utils/posthog.go b/pkg/utils/posthog.go
index 6383c10..9a9fb41 100644
--- a/pkg/utils/posthog.go
+++ b/pkg/utils/posthog.go
@@ -156,6 +156,48 @@ func (p *PosthogCliClient) CaptureFailedCodeownersGenerateContributorInsight() e
return nil
}
+// CaptureConfigGenerate gathers telemetry on success
+func (p *PosthogCliClient) CaptureConfigGenerate() error {
+ if p.activated {
+ return p.client.Enqueue(posthog.Capture{
+ DistinctId: p.uniqueID,
+ Event: "pizza_cli_generated_config",
+ })
+ }
+
+ return nil
+}
+
+// CaptureConfigGenerateMode gathers what mode a user is in when generating
+// either 'Automatic' (default) or 'Interactive'
+func (p *PosthogCliClient) CaptureConfigGenerateMode(mode string) error {
+ properties := make(map[string]interface{})
+
+ properties["mode"] = mode
+
+ if p.activated {
+ return p.client.Enqueue(posthog.Capture{
+ DistinctId: p.uniqueID,
+ Event: "pizza_cli_generated_config_mode",
+ Properties: properties,
+ })
+ }
+
+ return nil
+}
+
+// CaptureFailedConfigGenerate gathers telemetry on failed
+func (p *PosthogCliClient) CaptureFailedConfigGenerate() error {
+ if p.activated {
+ return p.client.Enqueue(posthog.Capture{
+ DistinctId: p.uniqueID,
+ Event: "pizza_cli_failed_to_generate_config",
+ })
+ }
+
+ return nil
+}
+
// CaptureInsights gathers telemetry on successful Insights command runs
func (p *PosthogCliClient) CaptureInsights() error {
if p.activated {
diff --git a/telemetry.go b/telemetry.go
index 71d910a..7f2c74a 100644
--- a/telemetry.go
+++ b/telemetry.go
@@ -36,6 +36,21 @@ func main() {
panic(err)
}
+ err = client.CaptureConfigGenerate()
+ if err != nil {
+ panic(err)
+ }
+
+ err = client.CaptureFailedConfigGenerate()
+ if err != nil {
+ panic(err)
+ }
+
+ err = client.CaptureConfigGenerateMode("interactive")
+ if err != nil {
+ panic(err)
+ }
+
err = client.CaptureCodeownersGenerateAuth("test-user")
if err != nil {
panic(err)