diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a54a5f73..d8037db5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,56 +1,62 @@ -name: build +name: Build on: push: + pull_request: + branches: [master] jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest] - name: build + name: Build runs-on: ${{ matrix.os }} steps: - - name: check out code - uses: actions/checkout@v2 - - name: setup Go 1.21 + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Go id: go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: ^1.21 - - name: build + go-version: ^1.22 + - name: Install golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.59 + - name: Install dependency + run: if [ $(uname) == "Darwin" ]; then brew install gnu-sed ;fi + - name: Build run: make build - - name: install dependency - run: if [ $(uname) == "Darwin" ]; then brew install gnu-sed ;fi - - name: run Unit tests. - run: go install github.com/go-delve/delve/cmd/dlv@latest && go test ./... -v -covermode=count -coverprofile=coverage.txt - - name: upload Coverage report to CodeCov - uses: codecov/codecov-action@v2 + - name: Run unit tests + run: go install github.com/go-delve/delve/cmd/dlv@latest && go test ./... -v -covermode=count -coverprofile=coverage.txt + - name: Upload Coverage report to CodeCov + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt verbose: true push_to_docker_latest: - name: push master code to docker latest image + name: Push master code to docker latest image if: github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - name: set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: login to DockerHub - uses: docker/login-action@v1 + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: build and push + - name: Build and push id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: push: true platforms: linux/amd64,linux/arm64 tags: cosmtrek/air:latest - - name: show image digest + - name: Show image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e093ba6b..431ad257 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,52 +1,54 @@ -name: release +name: Release on: push: pull_request: - branches: [ master ] + branches: [master] jobs: release: - name: release + name: Release runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: - - name: checkout code - uses: actions/checkout@v2 - - name: setup Go - uses: actions/setup-go@v2 + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v4 with: - go-version: ^1.21 - - name: set GOVERSION + go-version: ^1.22 + + - name: Set GOVERSION run: echo "GOVERSION=$(go version | sed -r 's/go version go(.*)\ .*/\1/')" >> $GITHUB_ENV - - name: set AirVersion + - name: Set AirVersion run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - name: show version + - name: Show version run: echo ${{ env.GOVERSION }} ${{ env.VERSION }} - - name: run GoReleaser - uses: goreleaser/goreleaser-action@v2 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 with: + distribution: goreleaser version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - name: set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: login to DockerHub - uses: docker/login-action@v1 + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: push to docker hub + - name: Push to DockerHub id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: push: true platforms: linux/amd64,linux/arm64 tags: cosmtrek/air:${{ env.VERSION }} - - name: show docker image digest - run: echo ${{ steps.docker_build.outputs.digest }} + - name: Show docker image digest + run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file diff --git a/.github/workflows/smoke_test.yml b/.github/workflows/smoke_test.yml index 0782b208..123c9711 100644 --- a/.github/workflows/smoke_test.yml +++ b/.github/workflows/smoke_test.yml @@ -1,18 +1,19 @@ -name: smoke_test +name: Smoke test on: push: + pull_request: jobs: smoke_test_ubuntu: - uses: cosmtrek/air/.github/workflows/smoke_test_reuse_job.yml@add_smoke_test - with: + uses: air-verse/air/.github/workflows/smoke_test_reuse_job.yml@master + with: run_on: ubuntu-latest smoke_test_macos: - uses: cosmtrek/air/.github/workflows/smoke_test_reuse_job.yml@add_smoke_test - with: + uses: air-verse/air/.github/workflows/smoke_test_reuse_job.yml@master + with: run_on: macos-latest smoke_test_windows: - uses: cosmtrek/air/.github/workflows/smoke_test_window_reust_job.yml@fix_window_arg_bug - with: + uses: air-verse/air/.github/workflows/smoke_test_reuse_job_windows.yml@master + with: run_on: windows-latest diff --git a/.github/workflows/smoke_test_reuse_job.yml b/.github/workflows/smoke_test_reuse_job.yml index 7bdf28f7..889204eb 100644 --- a/.github/workflows/smoke_test_reuse_job.yml +++ b/.github/workflows/smoke_test_reuse_job.yml @@ -1,4 +1,4 @@ -name: Reusable smoke_test +name: Reusable smoke test on: workflow_call: @@ -9,29 +9,29 @@ on: jobs: smoke_test: - name: smoke_test + name: Smoke test runs-on: ${{ inputs.run_on }} steps: - - name: check out code - uses: actions/checkout@v2 - - name: setup Go 1.21 + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Go id: go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: ^1.21 - - name: install + go-version: ^1.22 + - name: Install run: make install - - name: check rebuild + - name: Check rebuild id: check_rebuild working-directory: ./smoke_test/check_rebuild - run: | + run: | nohup air > nohup.out 2> nohup.err < /dev/null & sleep 15 echo "" >> main.go sleep 5 - cat nohup.out + cat nohup.out grep "running" nohup.out | wc -l | if [ "$(cat -)" -eq "2" ]; then echo "::set-output name=value::PASS"; else echo "::set-output name=value::FAIL"; fi - - uses: nick-invision/assert-action@v1 + - uses: nick-invision/assert-action@v2 with: expected: "PASS" - actual: ${{ steps.check_rebuild.outputs.value }} + actual: ${{ steps.check_rebuild.outputs.value }} \ No newline at end of file diff --git a/.github/workflows/smoke_test_window_reust_job.yml b/.github/workflows/smoke_test_reuse_job_windows.yml similarity index 58% rename from .github/workflows/smoke_test_window_reust_job.yml rename to .github/workflows/smoke_test_reuse_job_windows.yml index 027cc1b4..ec707558 100644 --- a/.github/workflows/smoke_test_window_reust_job.yml +++ b/.github/workflows/smoke_test_reuse_job_windows.yml @@ -1,4 +1,4 @@ -name: Reusable smoke_test +name: Reusable smoke test on Windows on: workflow_call: @@ -9,31 +9,31 @@ on: jobs: smoke_test: - name: smoke_test + name: Smoke test runs-on: ${{ inputs.run_on }} steps: - - name: check out code - uses: actions/checkout@v2 - - name: setup Go 1.21 + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Go id: go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: ^1.21 + go-version: ^1.22 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install depend run: | python -m pip install pexpect - - name: install + - name: Install run: make install - - name: check rebuild + - name: Check rebuild id: check_rebuild working-directory: ./smoke_test - run: | - python smoke_test.py - - uses: nick-invision/assert-action@v1 + run: | + python smoke_test.py + - uses: nick-invision/assert-action@v2 with: expected: "PASS" actual: ${{ steps.check_rebuild.outputs.value }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..c84882c6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,16 @@ +run: + timeout: 2m + +linters: + disable-all: true + enable: + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. + - gci # Gci controls Go package import order and makes it always deterministic + - goimports # checks that goimports was run + - ineffassign # Detects when assignments to existing variables are not used + - misspell # spell checker + - revive # configurable linter for Go. Drop-in replacement of golint + - staticcheck # go vet on steroids + - stylecheck # static analysis, finds bugs and performance issues, offers simplifications, and enforces style rules + - unconvert # Remove unnecessary type conversions + - unused # Checks Go code for unused constants, variables, functions and types diff --git a/Dockerfile b/Dockerfile index cb39ce18..3175b207 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM golang:1.21 AS builder +FROM golang:1.22 AS builder -MAINTAINER Rick Yu +LABEL maintainer="Rick Yu " ENV GOPATH /go ENV GO111MODULE on @@ -12,7 +12,7 @@ RUN --mount=type=cache,target=/go/pkg/mod go mod download RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build make ci && make install -FROM golang:1.21 +FROM golang:1.22 COPY --from=builder /go/bin/air /go/bin/air diff --git a/Makefile b/Makefile index d9943dab..d2fc38ac 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,21 @@ LDFLAGS += -X "main.airVersion=$(AIRVER)" LDFLAGS += -X "main.goVersion=$(shell go version | sed -r 's/go version go(.*)\ .*/\1/')" GO := GO111MODULE=on CGO_ENABLED=0 go +GOLANGCI_LINT_VERSION = v1.56.2 .PHONY: init -init: - go install golang.org/x/lint/golint@latest +init: install-golangci-lint go install golang.org/x/tools/cmd/goimports@latest @echo "Install pre-commit hook" @ln -s $(shell pwd)/hooks/pre-commit $(shell pwd)/.git/hooks/pre-commit || true @chmod +x ./hack/check.sh +.PHONY: install-golangci-lint +install-golangci-lint: +ifeq (, $(shell which golangci-lintx)) + @$(shell curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin $(GOLANGCI_LINT_VERSION)) +endif + .PHONY: setup setup: init git init diff --git a/README-zh_cn.md b/README-zh_cn.md index 89f2fdda..2e0084b1 100644 --- a/README-zh_cn.md +++ b/README-zh_cn.md @@ -1,10 +1,10 @@ -# Air [![Go](https://github.com/cosmtrek/air/workflows/Go/badge.svg)](https://github.com/cosmtrek/air/actions?query=workflow%3AGo+branch%3Amaster) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/dcb95264cc504cad9c2a3d8b0795a7f8)](https://www.codacy.com/gh/cosmtrek/air/dashboard?utm_source=github.com&utm_medium=referral&utm_content=cosmtrek/air&utm_campaign=Badge_Grade) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmtrek/air)](https://goreportcard.com/report/github.com/cosmtrek/air) [![codecov](https://codecov.io/gh/cosmtrek/air/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmtrek/air) +# Air [![Go](https://github.com/air-verse/air/workflows/Go/badge.svg)](https://github.com/air-verse/air/actions?query=workflow%3AGo+branch%3Amaster) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/dcb95264cc504cad9c2a3d8b0795a7f8)](https://www.codacy.com/gh/air-verse/air/dashboard?utm_source=github.com&utm_medium=referral&utm_content=air-verse/air&utm_campaign=Badge_Grade) [![Go Report Card](https://goreportcard.com/badge/github.com/air-verse/air)](https://goreportcard.com/report/github.com/air-verse/air) [![codecov](https://codecov.io/gh/air-verse/air/branch/master/graph/badge.svg)](https://codecov.io/gh/air-verse/air) :cloud: 热重载 Go 应用的工具 ![air](docs/air.png) -[English](README.md) | 简体中文 +[English](README.md) | 简体中文 | [繁體中文](README-zh_tw.md) ## 开发动机 @@ -22,47 +22,60 @@ Air 是为 Go 应用开发设计的另外一个热重载的命令行工具。只 * 在 Air 启动之后,允许监听新创建的路径 * 更棒的构建过程 -### ✨ beta 版本的特性 +### 使用参数覆盖指定配置 支持使用参数来配置 air 字段: 如果你只是想配置构建命令和运行命令,您可以直接使用以下命令,而无需配置文件: -`air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api"` +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" +``` 对于以列表形式输入的参数,使用逗号来分隔项目: -`air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" --build.exclude_dir "templates,build"` +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" --build.exclude_dir "templates,build" +``` ## 安装 -### 推荐使用 install.sh +### 使用 `go install` (推荐) + +使用 go 1.22 或更高版本: + +```shell +go install github.com/air-verse/air@latest +``` + +### 使用 install.sh -```bash +```shell # binary 文件会是在 $(go env GOPATH)/bin/air -curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin # 或者把它安装在 ./bin/ 路径下 -curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s air -v ``` -P.S. 非常感谢 mattn 的 [PR](https://github.com/cosmtrek/air/pull/1),使得 Air 支持 Windows 平台。 -### 使用 `go install` +### 使用 [goblin.run](https://goblin.run) -使用 Go 的版本为 1.16 或更高: +```shell +# binary 将会安装到 /usr/local/bin/air +curl -sSfL https://goblin.run/github.com/cosmtrek/air | sh -```bash -go install github.com/cosmtrek/air@latest +# 自定义路径安装 +curl -sSfL https://goblin.run/github.com/cosmtrek/air | PREFIX=/tmp sh ``` -### Docker +### Docker/Podman 请拉取这个 Docker 镜像 [cosmtrek/air](https://hub.docker.com/r/cosmtrek/air). -```bash +```shell docker run -it --rm \ -w "" \ -e "air_wd=" \ @@ -72,42 +85,71 @@ docker run -it --rm \ -c ``` -例如,我的项目之一是在 Docker 上运行的: +#### Docker/Podman .${SHELL}rc + +如果你想像正常应用程序一样连续使用 air,你可以在你的 ${SHELL}rc(Bash, Zsh 等)中创建一个函数 + +```shell +air() { + podman/docker run -it --rm \ + -w "$PWD" -v "$PWD":"$PWD" \ + -p "$AIR_PORT":"$AIR_PORT" \ + docker.io/cosmtrek/air "$@" +} +``` + +`` 是容器中的项目路径,例如:`/go/example` +如果你想进入容器,请添加 `--entrypoint=bash`。 + +
+ 例如 + +我的一个项目运行在 Docker 中: -```bash +```shell docker run -it --rm \ - -w "/go/src/github.com/cosmtrek/hub" \ - -v $(pwd):/go/src/github.com/cosmtrek/hub \ - -p 9090:9090 \ - cosmtrek/air + -w "/go/src/github.com/cosmtrek/hub" \ + -v $(pwd):/go/src/github.com/cosmtrek/hub \ + -p 9090:9090 \ + cosmtrek/air +``` + +另一个例子: + +```shell +cd /go/src/github.com/cosmtrek/hub +AIR_PORT=8080 air -c "config.toml" ``` +这将用当前目录替换 `$PWD`,`$AIR_PORT` 是要发布的端口,而 `$@` 用于接受应用程序本身的参数,例如 `-c` +
+ ## 使用方法 -您可以添加 `alias air='~/.air'` 到您的 `.bashrc` 或 `.zshrc` 后缀的文件. +为了方便输入,您可以添加 `alias air='~/.air'` 到您的 `.bashrc` 或 `.zshrc` 文件中. 首先,进入你的项目文件夹 -```bash +```shell cd /path/to/your_project ``` 最简单的方法是执行 -```bash +```shell # 优先在当前路径查找 `.air.toml` 后缀的文件,如果没有找到,则使用默认的 air -c .air.toml ``` -您可以运行以下命令初始化,把默认配置添加到当前路径下的`.air.toml` 文件。 +您可以运行以下命令,将具有默认设置的 `.air.toml` 配置文件初始化到当前目录。 -```bash +```shell air init ``` -在这之后,你只需执行 `air` 命令,无需添加额外的变量,它就能使用 `.air.toml` 文件中的配置了。 +在这之后,你只需执行 `air` 命令,无需额外参数,它就能使用 `.air.toml` 文件中的配置了。 -```bash +```shell air ``` @@ -117,7 +159,7 @@ air 您可以通过把变量添加在 air 命令之后来传递参数。 -```bash +```shell # 会执行 ./tmp/main bench air bench @@ -125,9 +167,9 @@ air bench air server --port 8080 ``` -You can separate the arguments passed for the air command and the built binary with `--` argument. +你可以使用 `--` 参数分隔传递给 air 命令和已构建二进制文件的参数。 -```bash +```shell # 会运行 ./tmp/main -h air -- -h @@ -135,9 +177,9 @@ air -- -h air -c .air.toml -- -h ``` -### Docker-compose +### Docker Compose -``` +```yaml services: my-project-with-air: image: cosmtrek/air @@ -155,23 +197,87 @@ services: ### 调试 -运行 `air -d` 命令能打印所有日志。 +`air -d` 命令能打印所有日志。 + +## Docker 用户安装和使用指南(如果不想使用 air 镜像) + +`Dockerfile` + +```Dockerfile +# 选择你想要的版本,>= 1.16 +FROM golang:1.22-alpine + +WORKDIR /app + +RUN go install github.com/cosmtrek/air@latest + +COPY go.mod go.sum ./ +RUN go mod download + +CMD ["air", "-c", ".air.toml"] +``` + +`docker-compose.yaml` + +```yaml +version: "3.8" +services: + web: + build: + context: . + # 修改为你的 Dockerfile 路径 + dockerfile: Dockerfile + ports: + - 8080:3000 + # 为了实时重载,将代码目录绑定到 /app 目录是很重要的 + volumes: + - ./:/app +``` ## Q&A ### 遇到 "command not found: air" 或 "No such file or directory" 该怎么办? -```zsh +```shell export GOPATH=$HOME/xxxxx export PATH=$PATH:$GOROOT/bin:$GOPATH/bin export PATH=$PATH:$(go env GOPATH)/bin <---- 请确认这行在您的配置信息中!!! ``` -## 部署 +### 在 wsl 下 bin 中包含 ' 时的错误 + +应该使用 `\` 来转义 bin 中的 `'`。相关问题:[#305](https://github.com/cosmtrek/air/issues/305) + +### 问题:如何只进行热编译而不运行? + +[#365](https://github.com/cosmtrek/air/issues/365) + +```toml +[build] + cmd = "/usr/bin/true" +``` + +### 如何在静态文件更改时自动重新加载浏览器? + + +请参考 [#512](https://github.com/cosmtrek/air/issues/512). + +* 确保你的静态文件在 `include_dir`、`include_ext` 或 `include_file` 中。 +* 确保你的 HTML 有一个 `` 标签。 +* 通过配置以下内容开启代理: + +```toml +[proxy] + enabled = true + proxy_port = + app_port = +``` + +## 开发 请注意:这需要 Go 1.16+ ,因为我使用 `go mod` 来管理依赖。 -```bash +```shell # 1. 首先复刻(fork)这个项目 # 2. 其次克隆(clone)它 @@ -179,7 +285,7 @@ mkdir -p $GOPATH/src/github.com/cosmtrek cd $GOPATH/src/github.com/cosmtrek git clone git@github.com:/air.git -# 3. 再次安装依赖 +# 3. 安装依赖 cd air make ci @@ -191,7 +297,7 @@ make install ### 发布新版本 -``` +```shell # 1. checkout 到 master 分支 git checkout master @@ -201,18 +307,18 @@ git tag v1.xx.x # 3. 推送到远程 git push origin v1.xx.x -ci 会加工和处理,然后会发布新版本。等待大约五分钟,你就能获取到新版本了。 +CI 将处理并发布新版本。等待大约 5 分钟,你就可以获取最新版本了。 ``` -## 赞助 +## Star 历史 + +[![Star History Chart](https://api.star-history.com/svg?repos=cosmtrek/air&type=Date)](https://star-history.com/#cosmtrek/air&Date) -Buy Me A Coffee +## 赞助 -衷心感谢以下的支持者。我一直铭记着你们的善意。 +[![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/cosmtrek) -* Peter Aba -* Apostolis Anastasiou -* keita koga +非常感谢众多支持者。我一直铭记你们的善意。 ## 许可证 diff --git a/README-zh_tw.md b/README-zh_tw.md new file mode 100644 index 00000000..08cc6f40 --- /dev/null +++ b/README-zh_tw.md @@ -0,0 +1,307 @@ +# :cloud: Air - Live reload for Go apps + +[![Go](https://github.com/air-verse/air/actions/workflows/release.yml/badge.svg)](https://github.com/air-verse/air/actions?query=workflow%3AGo+branch%3Amaster) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/dcb95264cc504cad9c2a3d8b0795a7f8)](https://www.codacy.com/gh/air-verse/air/dashboard?utm_source=github.com&utm_medium=referral&utm_content=air-verse/air&utm_campaign=Badge_Grade) [![Go Report Card](https://goreportcard.com/badge/github.com/air-verse/air)](https://goreportcard.com/report/github.com/air-verse/air) [![codecov](https://codecov.io/gh/air-verse/air/branch/master/graph/badge.svg)](https://codecov.io/gh/air-verse/air) + +![air](docs/air.png) + +[English](README.md) | [简体中文](README-zh_cn.md) | 繁體中文 + +## 開發動機 + +當我開始用 Go 開發網站並使用[gin](https://github.com/gin-gonic/gin)框架時,感到可惜的是 gin 缺乏自動重新編譯執行的方式。因此,我四處搜尋並嘗試使用[fresh](https://github.com/pilu/fresh),但它似乎不夠彈性,所以我打算重新寫得更好。最後,Air 就這麼誕生了。另外,非常感謝[pilu](https://github.com/pilu),如果沒有 fresh,就不會有 air :) + +Air 是一個另類的自動重新編譯執行命令列工具,用於開發 Go 應用。在你的項目根目錄下運行 `air`,將它執行於背景中,並專注於你的程式碼。 + +注意:此工具與生產環境的熱部署無關。 + +## 功能列表 + +* 彩色的日誌輸出 +* 自訂建立或任何命令 +* 支援排除子目錄 +* 允許在 Air 開始後監視新目錄 +* 更佳的建置過程 + +### 用參數覆寫指定的配置 + +支援將 air 做為參數的配置字段: + +如果你想設定建置命令和執行命令,你可以在不需要配置檔案的情況下如下使用命令: + +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api"` +``` + +對於需要輸入列表的參數,可以使用逗號將項目分隔: + +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" --build.exclude_dir "templates,build" +``` + +## 安裝 + +### 使用 `go install` (推薦) + +需要使用 go 1.22 或更高版本: + +```bash +go install github.com/air-verse/air@latest +``` + +### 透過 install.sh + +```shell +# binary will be $(go env GOPATH)/bin/air +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin + +# or install it into ./bin/ +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s + +air -v +``` + +### 透過 [goblin.run](https://goblin.run) + +```shell +# binary will be /usr/local/bin/air +curl -sSfL https://goblin.run/github.com/air-verse/air | sh + +# to put to a custom path +curl -sSfL https://goblin.run/github.com/air-verse/air | PREFIX=/tmp sh +``` + +### 透過 `go install` + +使用 go 1.18 或更高版本: + +```bash +go install github.com/air-verse/air@latest +``` + +### Docker/Podman + +請讀取 Docker 映像檔 [cosmtrek/air](https://hub.docker.com/r/cosmtrek/air). + +```shell +docker/podman run -it --rm \ + -w "" \ + -e "air_wd=" \ + -v $(pwd): \ + -p : \ + cosmtrek/air + -c +``` + +#### Docker/Podman .${SHELL}rc + +如果你想像常規應用程式一樣持續使用 air,你可以在你的 ${SHELL}rc (Bash, Zsh, etc…) 中創建一個函數。 + +```shell +air() { + podman/docker run -it --rm \ + -w "$PWD" -v "$PWD":"$PWD" \ + -p "$AIR_PORT":"$AIR_PORT" \ + docker.io/cosmtrek/air "$@" +} +``` + +`` 是你的容器中的專案路徑,例如:/go/example 如果你想要進入容器,請加上 --entrypoint=bash。 + +
+ For example + +我其中一個專案是在 Docker 中運行 + +```shell +docker run -it --rm \ + -w "/go/src/github.com/cosmtrek/hub" \ + -v $(pwd):/go/src/github.com/cosmtrek/hub \ + -p 9090:9090 \ + cosmtrek/air +``` + +另一個例子 + +```shell +cd /go/src/github.com/cosmtrek/hub +AIR_PORT=8080 air -c "config.toml" +``` + +這將會用當前目錄替換 `$PWD`,`$AIR_PORT` 是發佈的端口,`$@` 是用來接受應用程式本身的參數,例如 -c + +
+ +## 使用方式 + +為了減少輸入,你可以將 `alias air='~/.air'` 加到你的 `.bashrc` 或者 `.zshrc`。 + +首先,進入你的專案目錄 + +```shell +cd /path/to/your_project +``` + +最簡單的使用方式是運行 + +```shell +# firstly find `.air.toml` in current directory, if not found, use defaults +air -c .air.toml +``` + +你可以用以下命令初始化 `.air.toml` 配置檔到當前目錄,並使用預設設置。 + +```shell +air init +``` + +此後,你可以只運行 `air` 命令,而不需要額外的參數,它將使用 `.air.toml` 檔案作為配置。 + +```shell +air +``` + +要修改配置,請參閱 [air_example.toml](air_example.toml) 檔案。 + +### 運行時參數 + +你可以在 air 命令後添加參數來運行已構建的二進制檔。 + +```shell +# Will run ./tmp/main bench +air bench + +# Will run ./tmp/main server --port 8080 +air server --port 8080 +``` + +你可以使用 `--` 參數來分隔傳遞給 air 命令和已建構的二進制檔的參數。 + +```shell +# Will run ./tmp/main -h +air -- -h + +# Will run air with custom config and pass -h argument to the built binary +air -c .air.toml -- -h +``` + +### Docker Compose + +```yaml +services: + my-project-with-air: + image: cosmtrek/air + # working_dir value has to be the same of mapped volume + working_dir: /project-package + ports: + - : + environment: + - ENV_A=${ENV_A} + - ENV_B=${ENV_B} + - ENV_C=${ENV_C} + volumes: + - ./project-relative-path/:/project-package/ +``` + +### 除錯 + +`air -d` prints all logs. + +## 對於不想使用 air 映像的 Docker 使用者的安裝與使用方法 + +`Dockerfile` + +```Dockerfile +# Choose whatever you want, version >= 1.16 +FROM golang:1.21-alpine + +WORKDIR /app + +RUN go install github.com/air-verse/air@latest + +COPY go.mod go.sum ./ +RUN go mod download + +CMD ["air", "-c", ".air.toml"] +``` + +`docker-compose.yaml` + +```yaml +version: "3.8" +services: + web: + build: + context: . + # Correct the path to your Dockerfile + dockerfile: Dockerfile + ports: + - 8080:3000 + # Important to bind/mount your codebase dir to /app dir for live reload + volumes: + - ./:/app +``` + +## Q&A + +### "找不到命令:air" 或者 "找不到檔案或目錄" + +```shell +export GOPATH=$HOME/xxxxx +export PATH=$PATH:$GOROOT/bin:$GOPATH/bin +export PATH=$PATH:$(go env GOPATH)/bin <---- Confirm this line in you profile!!! +``` + +### 當 bin 中包含 ' 時,在 wsl 下的錯誤 + +應該使用 `\` 來轉義 bin 中的 `'。相關議題:[#305](https://github.com/air-verse/air/issues/305) + +## 開發 + +請注意,由於我使用 `go mod` 來管理依賴,所以需要 Go 1.16+。 + +```shell +# Fork this project + +# Clone it +mkdir -p $GOPATH/src/github.com/cosmtrek +cd $GOPATH/src/github.com/cosmtrek +git clone git@github.com:/air.git + +# Install dependencies +cd air +make ci + +# Explore it and happy hacking! +make install +``` + +歡迎提出 Pull Request + +### 發佈版本 + +```shell +# Checkout to master +git checkout master + +# Add the version that needs to be released +git tag v1.xx.x + +# Push to remote +git push origin v1.xx.x + +# The CI will process and release a new version. Wait about 5 min, and you can fetch the latest version +``` + +## 星星歷史 + +[![Star History Chart](https://api.star-history.com/svg?repos=air-verse/air&type=Date)](https://star-history.com/#air-verse/air&Date) + +## 贊助專案 + +[![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/cosmtrek) + +非常感謝大量的支持者。我一直記得你們的善意。 + +## 授權 + +[GNU General Public License v3.0](LICENSE) diff --git a/README.md b/README.md index 2e7a744e..59ad8a01 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # :cloud: Air - Live reload for Go apps -[![Go](https://github.com/cosmtrek/air/actions/workflows/release.yml/badge.svg)](https://github.com/cosmtrek/air/actions?query=workflow%3AGo+branch%3Amaster) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/dcb95264cc504cad9c2a3d8b0795a7f8)](https://www.codacy.com/gh/cosmtrek/air/dashboard?utm_source=github.com&utm_medium=referral&utm_content=cosmtrek/air&utm_campaign=Badge_Grade) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmtrek/air)](https://goreportcard.com/report/github.com/cosmtrek/air) [![codecov](https://codecov.io/gh/cosmtrek/air/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmtrek/air) +[![Go](https://github.com/air-verse/air/actions/workflows/release.yml/badge.svg)](https://github.com/air-verse/air/actions?query=workflow%3AGo+branch%3Amaster) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/dcb95264cc504cad9c2a3d8b0795a7f8)](https://www.codacy.com/gh/air-verse/air/dashboard?utm_source=github.com&utm_medium=referral&utm_content=air-verse/air&utm_campaign=Badge_Grade) [![Go Report Card](https://goreportcard.com/badge/github.com/air-verse/air)](https://goreportcard.com/report/github.com/air-verse/air) [![codecov](https://codecov.io/gh/air-verse/air/branch/master/graph/badge.svg)](https://codecov.io/gh/air-verse/air) ![air](docs/air.png) -English | [简体中文](README-zh_cn.md) +English | [简体中文](README-zh_cn.md) | [繁體中文](README-zh_tw.md) ## Motivation @@ -26,46 +26,60 @@ Note: This tool has nothing to do with hot-deploy for production. * Allow watching new directories after Air started * Better building process -### ✨ beta feature +### Overwrite specify configuration from arguments Support air config fields as arguments: If you want to config build command and run command, you can use like the following command without the config file: -`air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api"` +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" +``` Use a comma to separate items for arguments that take a list as input: -`air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" --build.exclude_dir "templates,build"` +```shell +air --build.cmd "go build -o bin/api cmd/run.go" --build.bin "./bin/api" --build.exclude_dir "templates,build" +``` ## Installation -### Prefer install.sh +### Via `go install` (Recommended) + +With go 1.22 or higher: ```bash +go install github.com/air-verse/air@latest +``` + +### Via install.sh + +```shell # binary will be $(go env GOPATH)/bin/air -curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin # or install it into ./bin/ -curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s +curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s air -v ``` -### Via `go install` +### Via [goblin.run](https://goblin.run) -With go 1.18 or higher: +```shell +# binary will be /usr/local/bin/air +curl -sSfL https://goblin.run/github.com/air-verse/air | sh -```bash -go install github.com/cosmtrek/air@latest +# to put to a custom path +curl -sSfL https://goblin.run/github.com/air-verse/air | PREFIX=/tmp sh ``` -### Docker +### Docker/Podman -Please pull this docker image [cosmtrek/air](https://hub.docker.com/r/cosmtrek/air). +Please pull this Docker image [cosmtrek/air](https://hub.docker.com/r/cosmtrek/air). -```bash -docker run -it --rm \ +```shell +docker/podman run -it --rm \ -w "" \ -e "air_wd=" \ -v $(pwd): \ @@ -74,18 +88,44 @@ docker run -it --rm \ -c ``` +#### Docker/Podman .${SHELL}rc + +if you want to use air continuously like a normal app, you can create a function in your ${SHELL}rc (Bash, Zsh, etc…) + +```shell +air() { + podman/docker run -it --rm \ + -w "$PWD" -v "$PWD":"$PWD" \ + -p "$AIR_PORT":"$AIR_PORT" \ + docker.io/cosmtrek/air "$@" +} +``` + +`` is your project path in container, eg: /go/example +if you want to enter the container, Please add --entrypoint=bash. +
For example -One of my project runs in docker: +One of my project runs in Docker: -```bash +```shell docker run -it --rm \ - -w "/go/src/github.com/cosmtrek/hub" \ - -v $(pwd):/go/src/github.com/cosmtrek/hub \ - -p 9090:9090 \ - cosmtrek/air + -w "/go/src/github.com/cosmtrek/hub" \ + -v $(pwd):/go/src/github.com/cosmtrek/hub \ + -p 9090:9090 \ + cosmtrek/air ``` + +Another example: + +```shell +cd /go/src/github.com/cosmtrek/hub +AIR_PORT=8080 air -c "config.toml" +``` + +this will replace `$PWD` with the current directory, `$AIR_PORT` is the port where to publish and `$@` is to accept arguments of the application itself for example -c +
## Usage @@ -94,26 +134,26 @@ For less typing, you could add `alias air='~/.air'` to your `.bashrc` or `.zshrc First enter into your project -```bash +```shell cd /path/to/your_project ``` The simplest usage is run -```bash +```shell # firstly find `.air.toml` in current directory, if not found, use defaults air -c .air.toml ``` You can initialize the `.air.toml` configuration file to the current directory with the default settings running the following command. -```bash +```shell air init ``` -After this, you can just run the `air` command without additional arguments and it will use the `.air.toml` file for configuration. +After this, you can just run the `air` command without additional arguments, and it will use the `.air.toml` file for configuration. -```bash +```shell air ``` @@ -123,7 +163,7 @@ For modifying the configuration refer to the [air_example.toml](air_example.toml You can pass arguments for running the built binary by adding them after the air command. -```bash +```shell # Will run ./tmp/main bench air bench @@ -133,7 +173,7 @@ air server --port 8080 You can separate the arguments passed for the air command and the built binary with `--` argument. -```bash +```shell # Will run ./tmp/main -h air -- -h @@ -141,9 +181,9 @@ air -- -h air -c .air.toml -- -h ``` -### Docker-compose +### Docker Compose -``` +```yaml services: my-project-with-air: image: cosmtrek/air @@ -166,13 +206,14 @@ services: ## Installation and Usage for Docker users who don't want to use air image `Dockerfile` + ```Dockerfile # Choose whatever you want, version >= 1.16 -FROM golang:1.21-alpine +FROM golang:1.22-alpine WORKDIR /app -RUN go install github.com/cosmtrek/air@latest +RUN go install github.com/air-verse/air@latest COPY go.mod go.sum ./ RUN go mod download @@ -181,6 +222,7 @@ CMD ["air", "-c", ".air.toml"] ``` `docker-compose.yaml` + ```yaml version: "3.8" services: @@ -200,7 +242,7 @@ services: ### "command not found: air" or "No such file or directory" -```zsh +```shell export GOPATH=$HOME/xxxxx export PATH=$PATH:$GOROOT/bin:$GOPATH/bin export PATH=$PATH:$(go env GOPATH)/bin <---- Confirm this line in you profile!!! @@ -208,13 +250,38 @@ export PATH=$PATH:$(go env GOPATH)/bin <---- Confirm this line in you profile!!! ### Error under wsl when ' is included in the bin -Should use `\` to escape the `' in the bin. related issue: [#305](https://github.com/cosmtrek/air/issues/305) +Should use `\` to escape the `' in the bin. related issue: [#305](https://github.com/air-verse/air/issues/305) + +### Question: how to do hot compile only and do not run anything? + +[#365](https://github.com/air-verse/air/issues/365) + +```toml +[build] + cmd = "/usr/bin/true" +``` + +### How to Reload the Browser Automatically on Static File Changes + + +Refer to issue [#512](https://github.com/air-verse/air/issues/512) for additional details. + +* Ensure your static files in `include_dir`, `include_ext`, or `include_file`. +* Ensure your HTML has a `` tag +* Activate the proxy by configuring the following config: + +```toml +[proxy] + enabled = true + proxy_port = + app_port = +``` ## Development Please note that it requires Go 1.16+ since I use `go mod` to manage dependencies. -```bash +```shell # Fork this project # Clone it @@ -234,7 +301,7 @@ Pull requests are welcome. ### Release -``` +```shell # Checkout to master git checkout master @@ -249,14 +316,14 @@ git push origin v1.xx.x ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=cosmtrek/air&type=Date)](https://star-history.com/#cosmtrek/air&Date) +[![Star History Chart](https://api.star-history.com/svg?repos=air-verse/air&type=Date)](https://star-history.com/#air-verse/air&Date) ## Sponsor -Buy Me A Coffee +[![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/cosmtrek) Give huge thanks to lots of supporters. I've always been remembering your kindness. ## License -[GNU General Public License v3.0](LICENSE) \ No newline at end of file +[GNU General Public License v3.0](LICENSE) diff --git a/air_example.toml b/air_example.toml index 01cec979..ac00e8c0 100644 --- a/air_example.toml +++ b/air_example.toml @@ -1,4 +1,4 @@ -# Config file for [Air](https://github.com/cosmtrek/air) in TOML format +# Config file for [Air](https://github.com/air-verse/air) in TOML format # Working directory # . or absolute path, please note that the directories following must be under root. @@ -6,12 +6,18 @@ root = "." tmp_dir = "tmp" [build] +# Array of commands to run before each build +pre_cmd = ["echo 'hello air' > pre_cmd.txt"] # Just plain old shell command. You could use `make` as well. cmd = "go build -o ./tmp/main ." +# Array of commands to run after ^C +post_cmd = ["echo 'hello air' > post_cmd.txt"] # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary, can setup environment variables when run your app. full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" +# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'. +args_bin = ["hello", "world"] # Watch these filename extensions. include_ext = ["go", "tpl", "tmpl", "html"] # Ignore these filename extensions or directories. @@ -41,13 +47,11 @@ stop_on_error = true # Send Interrupt signal before killing process (windows does not support this feature) send_interrupt = false # Delay after sending Interrupt signal -kill_delay = 500 # ms +kill_delay = 500 # nanosecond # Rerun binary or not rerun = false -# Delay after each executions +# Delay after each execution rerun_delay = 500 -# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'. -args_bin = ["hello", "world"] [log] # Show log time @@ -69,3 +73,9 @@ clean_on_exit = true [screen] clear_on_rebuild = true keep_scroll = true + +# Enable live-reloading on the browser. +[proxy] + enabled = true + proxy_port = 8090 + app_port = 8080 diff --git a/docs/air.png b/docs/air.png index 80c4cf5b..f0948f05 100644 Binary files a/docs/air.png and b/docs/air.png differ diff --git a/go.mod b/go.mod index 09b9fb25..e579d5eb 100644 --- a/go.mod +++ b/go.mod @@ -1,30 +1,36 @@ -module github.com/cosmtrek/air +module github.com/air-verse/air -go 1.21 +go 1.22 require ( dario.cat/mergo v1.0.0 - github.com/creack/pty v1.1.18 - github.com/fatih/color v1.14.1 - github.com/fsnotify/fsnotify v1.6.0 - github.com/gohugoio/hugo v0.111.3 + github.com/creack/pty v1.1.21 + github.com/fatih/color v1.16.0 + github.com/fsnotify/fsnotify v1.7.0 + github.com/gohugoio/hugo v0.123.3 github.com/pelletier/go-toml v1.9.5 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 ) require ( - github.com/bep/godartsass v0.16.0 // indirect - github.com/bep/golibsass v1.1.0 // indirect - github.com/cli/safeexec v1.0.0 // indirect + github.com/bep/godartsass v1.2.0 // indirect + github.com/bep/godartsass/v2 v2.0.0 // indirect + github.com/bep/golibsass v1.1.1 // indirect + github.com/cli/safeexec v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/tdewolff/parse/v2 v2.6.5 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/tdewolff/parse/v2 v2.7.12 // indirect + github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 61ee9a08..7293cc8f 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,118 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/bep/godartsass v0.16.0 h1:nTpenrZBQjVSjLkCw3AgnYmBB2czauTJa4BLLv448qg= -github.com/bep/godartsass v0.16.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8= -github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw= -github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= +github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= +github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= +github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M= +github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps= +github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU= +github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo= +github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840= +github.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY= +github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA= +github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c= +github.com/bep/godartsass v1.2.0 h1:E2VvQrxAHAFwbjyOIExAMmogTItSKodoKuijNrGm5yU= +github.com/bep/godartsass v1.2.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8= +github.com/bep/godartsass/v2 v2.0.0 h1:Ruht+BpBWkpmW+yAM2dkp7RSSeN0VLaTobyW0CiSP3Y= +github.com/bep/godartsass/v2 v2.0.0/go.mod h1:AcP8QgC+OwOXEq6im0WgDRYK7scDsmZCEW62o1prQLo= +github.com/bep/golibsass v1.1.1 h1:xkaet75ygImMYjM+FnHIT3xJn7H0xBA9UxSOJjk8Khw= +github.com/bep/golibsass v1.1.1/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= +github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY= +github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI= +github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI= +github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc= +github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ= +github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0= +github.com/bep/overlayfs v0.9.1 h1:SL54SV8A3zRkmQ+83Jj4TLE88jadHd5d1L4NpfmqJJs= +github.com/bep/overlayfs v0.9.1/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40= +github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI= +github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= +github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= +github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/evanw/esbuild v0.20.1 h1:ueyMIL19umCcJTSxiBH/QmPipgGt8hEDM24pdfowgEc= +github.com/evanw/esbuild v0.20.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/gohugoio/hugo v0.111.3 h1:m98NJv/5ivJLkQ4u3vPYsrAfBTnDIefZPGhnw/7xW80= -github.com/gohugoio/hugo v0.111.3/go.mod h1:1gb2es3022plbaNiZjhBTdpXN2cepIeqvBnL/NHnKLY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= +github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= +github.com/gohugoio/hugo v0.123.3 h1:a96Kex2xrqmrSYAYJ8MKzsKCVvCUPjW3+YyXtsEXRmE= +github.com/gohugoio/hugo v0.123.3/go.mod h1:7AHCGAy5MIFEhnvQMG5DfpVGpgrXfkoZ4z6y0zwQHLQ= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0 h1:oFQ3f1M3Ook6amHmbqVu/uBRrQ6yjMDFkIv4HQr0f1Y= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM= +github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc= +github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4= +github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo= +github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hairyhenderson/go-codeowners v0.4.0 h1:Wx/tRXb07sCyHeC8mXfio710Iu35uAy5KYiBdLHdv4Q= +github.com/hairyhenderson/go-codeowners v0.4.0/go.mod h1:iJgZeCt+W/GzXo5uchFCqvVHZY2T4TAIpvuVlKVkLxc= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= +github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -154,347 +120,141 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= +github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE= +github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= +github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0= +github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc= +github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= +github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= +github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tdewolff/parse/v2 v2.6.5 h1:lYvWBk55GkqKl0JJenGpmrgu/cPHQQ6/Mm1hBGswoGQ= -github.com/tdewolff/parse/v2 v2.6.5/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= -github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= -github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tdewolff/minify/v2 v2.20.17 h1:zGqEDhspr3XjSrQI/56vw9IdAhLAaKTLXWnDBsxNVt8= +github.com/tdewolff/minify/v2 v2.20.17/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM= +github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ= +github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= +github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= +github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hack/check.sh b/hack/check.sh index 5b215f81..89563ed2 100755 --- a/hack/check.sh +++ b/hack/check.sh @@ -8,9 +8,9 @@ exit_code=0 check_scope=$1 if [[ "${check_scope}" = "all" ]]; then echo "all" - files=($(git ls-files | grep "\.go" | grep -v -e "^third_party" -e "^vendor")) + files=($(git ls-files | grep "\.go$" | grep -v -e "^third_party" -e "^vendor")) else - files=($(git diff --cached --name-only --diff-filter ACM | grep "\.go" | grep -v -e "^third_party" -e "^vendor")) + files=($(git diff --cached --name-only --diff-filter ACM | grep "\.go$" | grep -v -e "^third_party" -e "^vendor")) fi echo -e "${green}1. Formatting code style" @@ -19,13 +19,12 @@ if [[ "${#files[@]}" -ne 0 ]]; then fi echo -e "${green}2. Linting" -for file in "${files[@]}"; do - out=$(golint ${file}) - if [[ -n "${out}" ]]; then - echo "${red}${out}" - exit_code=1 - fi -done +out=$(golangci-lint run) +if [[ -n "${out}" ]]; then + echo "${red}${out}" + exit_code=1 +fi + if [[ ${exit_code} -ne 0 ]]; then echo "${red}Please fix the errors above :)" diff --git a/install.sh b/install.sh index c4380c4a..52705c81 100755 --- a/install.sh +++ b/install.sh @@ -6,13 +6,13 @@ set -e usage() { this=$1 cat < 1 && os.Args[1] == "init" { - writeDefaultConfig() + configName, err := writeDefaultConfig() + if err != nil { + log.Fatalf("Failed writing default config: %+v", err) + } + fmt.Printf("%s file created to the current directory with the default settings\n", configName) return } @@ -110,7 +114,7 @@ func (e *Engine) checkRunEnv() error { } func (e *Engine) watching(root string) error { - return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + return filepath.Walk(root, func(path string, info os.FileInfo, _ error) error { // NOTE: path is absolute if info != nil && !info.IsDir() { if e.checkIncludeFile(path) { @@ -307,6 +311,11 @@ func (e *Engine) isModified(filename string) bool { // Endless loop and never return func (e *Engine) start() { + if e.config.Proxy.Enabled { + go e.proxy.Run() + e.mainLog("Proxy server listening on http://localhost%s", e.proxy.server.Addr) + } + e.running = true firstRunCh := make(chan bool, 1) firstRunCh <- true @@ -329,6 +338,8 @@ func (e *Engine) start() { } } + // cannot set buldDelay to 0, because when the write multiple events received in short time + // it will start Multiple buildRuns: https://github.com/air-verse/air/issues/473 time.Sleep(e.config.buildDelay()) e.flushEvents() @@ -372,12 +383,16 @@ func (e *Engine) buildRun() { select { case <-e.buildRunStopCh: return - case <-e.canExit: default: } var err error + if err = e.runPreCmd(); err != nil { + e.runnerLog("failed to execute pre_cmd: %s", err.Error()) + if e.config.Build.StopOnError { + return + } + } if err = e.building(); err != nil { - e.canExit <- true e.buildLog("failed to build, error: %s", err.Error()) _ = e.writeBuildErrorLog(err.Error()) if e.config.Build.StopOnError { @@ -390,7 +405,6 @@ func (e *Engine) buildRun() { return case <-e.exitCh: e.mainDebug("exit in buildRun") - close(e.canExit) return default: } @@ -410,10 +424,9 @@ func (e *Engine) flushEvents() { } } -func (e *Engine) building() error { - var err error - e.buildLog("building...") - cmd, stdout, stderr, err := e.startCmd(e.config.Build.Cmd) +// utility to execute commands, such as cmd & pre_cmd +func (e *Engine) runCommand(command string) error { + cmd, stdout, stderr, err := e.startCmd(command) if err != nil { return err } @@ -423,7 +436,7 @@ func (e *Engine) building() error { }() _, _ = io.Copy(os.Stdout, stdout) _, _ = io.Copy(os.Stderr, stderr) - // wait for building + // wait for command to finish err = cmd.Wait() if err != nil { return err @@ -431,30 +444,48 @@ func (e *Engine) building() error { return nil } -func (e *Engine) runBin() error { - // control killFunc should be kill or not - killCh := make(chan struct{}) - wg := sync.WaitGroup{} - go func() { - // listen to binStopCh - // cleanup() will close binStopCh when engine stop - // start() will close binStopCh when file changed - <-e.binStopCh - close(killCh) +// run cmd option in .air.toml +func (e *Engine) building() error { + e.buildLog("building...") + err := e.runCommand(e.config.Build.Cmd) + if err != nil { + return err + } + return nil +} - select { - case <-e.exitCh: - wg.Wait() - close(e.canExit) - default: +// run pre_cmd option in .air.toml +func (e *Engine) runPreCmd() error { + for _, command := range e.config.Build.PreCmd { + e.runnerLog("> %s", command) + err := e.runCommand(command) + if err != nil { + return err } - }() + } + return nil +} - killFunc := func(cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, killCh chan struct{}, processExit chan struct{}, wg *sync.WaitGroup) { - defer wg.Done() +// run post_cmd option in .air.toml +func (e *Engine) runPostCmd() error { + for _, command := range e.config.Build.PostCmd { + e.runnerLog("> %s", command) + err := e.runCommand(command) + if err != nil { + return err + } + } + return nil +} + +func (e *Engine) runBin() error { + killFunc := func(cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, killCh chan struct{}, processExit chan struct{}) { select { - // the process haven't exited yet, kill it - case <-killCh: + // listen to binStopCh + // cleanup() will close binStopCh when engine stop + // start() will close binStopCh when file changed + case <-e.binStopCh: + close(killCh) break // the process is exited, return @@ -487,24 +518,57 @@ func (e *Engine) runBin() error { e.runnerLog("running...") go func() { + + defer func() { + select { + case <-e.exitCh: + e.mainDebug("exit in runBin") + default: + } + }() + + // control killFunc should be kill or not + killCh := make(chan struct{}) for { select { case <-killCh: return default: + e.procKillWg.Add(1) command := strings.Join(append([]string{e.config.Build.Bin}, e.runArgs...), " ") cmd, stdout, stderr, _ := e.startCmd(command) processExit := make(chan struct{}) e.mainDebug("running process pid %v", cmd.Process.Pid) + if e.config.Proxy.Enabled { + e.proxy.Reload() + } - wg.Add(1) - atomic.AddUint64(&e.round, 1) - go killFunc(cmd, stdout, stderr, killCh, processExit, &wg) - - _, _ = io.Copy(os.Stdout, stdout) - _, _ = io.Copy(os.Stderr, stderr) - _, _ = cmd.Process.Wait() + e.withLock(func() { + close(e.binStopCh) + e.binStopCh = make(chan bool) + go killFunc(cmd, stdout, stderr, killCh, processExit) + }) + + go func() { + _, _ = io.Copy(os.Stdout, stdout) + _, _ = cmd.Process.Wait() + }() + + go func() { + _, _ = io.Copy(os.Stderr, stderr) + _, _ = cmd.Process.Wait() + }() + state, _ := cmd.Process.Wait() close(processExit) + switch state.ExitCode() { + case 0: + e.runnerLog("Process Exit with Code 0") + case -1: + // because when we use ctrl + c to stop will return -1 + default: + e.runnerLog("Process Exit with Code: %v", state.ExitCode()) + } + e.procKillWg.Done() if !e.config.Build.Rerun { return @@ -521,11 +585,18 @@ func (e *Engine) cleanup() { e.mainLog("cleaning...") defer e.mainLog("see you again~") + if e.config.Proxy.Enabled { + e.mainDebug("powering down the proxy...") + if err := e.proxy.Stop(); err != nil { + e.mainLog("failed to stop proxy: %+v", err) + } + } + e.withLock(func() { close(e.binStopCh) e.binStopCh = make(chan bool) }) - e.mainDebug("wating for close watchers..") + e.mainDebug("waiting for close watchers..") e.withLock(func() { for i := 0; i < int(e.watchers); i++ { @@ -549,13 +620,15 @@ func (e *Engine) cleanup() { } e.mainDebug("waiting for exit...") - - <-e.canExit + e.procKillWg.Wait() e.running = false e.mainDebug("exited") } // Stop the air func (e *Engine) Stop() { + if err := e.runPostCmd(); err != nil { + e.runnerLog("failed to execute post_cmd, error: %s", err.Error()) + } close(e.exitCh) } diff --git a/runner/engine_test.go b/runner/engine_test.go index 28b55b23..a4529f49 100644 --- a/runner/engine_test.go +++ b/runner/engine_test.go @@ -3,7 +3,6 @@ package runner import ( "errors" "fmt" - "io/ioutil" "log" "net" "os" @@ -11,7 +10,7 @@ import ( "os/signal" "runtime" "strings" - "sync/atomic" + "sync" "syscall" "testing" "time" @@ -112,76 +111,77 @@ func TestRegexes(t *testing.T) { } } -func TestRerun(t *testing.T) { - tmpDir := initWithQuickExitGoCode(t) +func TestRunCommand(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) // change dir to tmpDir chdir(t, tmpDir) engine, err := NewEngine("", true) - engine.config.Build.ExcludeUnchanged = true - engine.config.Build.Rerun = true - engine.config.Build.RerunDelay = 100 if err != nil { t.Fatalf("Should not be fail: %s.", err) } - go func() { - engine.Run() - t.Logf("engine run") - }() - - time.Sleep(time.Second * 1) - - // stop engine - engine.Stop() - time.Sleep(time.Second * 1) - t.Logf("engine stopped") - - if atomic.LoadUint64(&engine.round) <= 1 { - t.Fatalf("The engine did't rerun") + err = engine.runCommand("touch test.txt") + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + if _, err := os.Stat("./test.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } } } -func TestRerunWhenFileChanged(t *testing.T) { - tmpDir := initWithQuickExitGoCode(t) +func TestRunPreCmd(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) // change dir to tmpDir chdir(t, tmpDir) engine, err := NewEngine("", true) - engine.config.Build.ExcludeUnchanged = true - engine.config.Build.Rerun = true - engine.config.Build.RerunDelay = 100 if err != nil { t.Fatalf("Should not be fail: %s.", err) } - go func() { - engine.Run() - t.Logf("engine run") - }() - time.Sleep(time.Second * 1) + engine.config.Build.PreCmd = []string{"echo 'hello air' > pre_cmd.txt"} + err = engine.runPreCmd() + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + if _, err := os.Stat("./pre_cmd.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } + } +} - roundBeforeChange := atomic.LoadUint64(&engine.round) +func TestRunPostCmd(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) + // change dir to tmpDir + chdir(t, tmpDir) - t.Logf("start change main.go") - // change file of main.go - // just append a new empty line to main.go - time.Sleep(time.Second * 2) - file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) + engine, err := NewEngine("", true) if err != nil { t.Fatalf("Should not be fail: %s.", err) } - defer file.Close() - _, err = file.WriteString("\n") + + engine.config.Build.PostCmd = []string{"echo 'hello air' > post_cmd.txt"} + err = engine.runPostCmd() if err != nil { t.Fatalf("Should not be fail: %s.", err) } - time.Sleep(time.Second * 1) - // stop engine - engine.Stop() - time.Sleep(time.Second * 1) - t.Logf("engine stopped") - - roundAfterChange := atomic.LoadUint64(&engine.round) - if roundBeforeChange+1 >= roundAfterChange { - t.Fatalf("The engine didn't rerun") + if _, err := os.Stat("./post_cmd.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } } } @@ -222,9 +222,12 @@ func TestRebuild(t *testing.T) { if err != nil { t.Fatalf("Should not be fail: %s.", err) } + wg := sync.WaitGroup{} + wg.Add(1) go func() { engine.Run() t.Logf("engine stopped") + wg.Done() }() err = waitingPortReady(t, port, time.Second*10) if err != nil { @@ -232,7 +235,7 @@ func TestRebuild(t *testing.T) { } t.Logf("port is ready") - // start rebuld + // start rebuild t.Logf("start change main.go") // change file of main.go @@ -260,8 +263,9 @@ func TestRebuild(t *testing.T) { t.Logf("port is ready") // stop engine engine.Stop() - time.Sleep(time.Second * 1) t.Logf("engine stopped") + wg.Wait() + time.Sleep(time.Second * 1) assert.True(t, checkPortConnectionRefused(port)) } @@ -287,7 +291,7 @@ func waitingPortConnectionRefused(t *testing.T, port int, timeout time.Duration) } func TestCtrlCWhenHaveKillDelay(t *testing.T) { - // fix https://github.com/cosmtrek/air/issues/278 + // fix https://github.com/air-verse/air/issues/278 // generate a random port data := []byte("[build]\n kill_delay = \"2s\"") c := Config{} @@ -309,14 +313,16 @@ func TestCtrlCWhenHaveKillDelay(t *testing.T) { engine.config.Build.KillDelay = c.Build.KillDelay engine.config.Build.Delay = 2000 engine.config.Build.SendInterrupt = true - engine.config.preprocess() + if err := engine.config.preprocess(); err != nil { + t.Fatalf("Should not be fail: %s.", err) + } go func() { engine.Run() t.Logf("engine stopped") }() sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) go func() { <-sigs engine.Stop() @@ -370,8 +376,43 @@ func TestCtrlCWhenREngineIsRunning(t *testing.T) { assert.False(t, engine.running) } +func TestCtrlCWithFailedBin(t *testing.T) { + timeout := 5 * time.Second + done := make(chan struct{}) + go func() { + dir := initWithQuickExitGoCode(t) + chdir(t, dir) + engine, err := NewEngine("", true) + assert.NoError(t, err) + engine.config.Build.Bin = "" + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + var wg sync.WaitGroup + wg.Add(1) + go func() { + engine.Run() + t.Logf("engine stopped") + wg.Done() + }() + go func() { + <-sigs + engine.Stop() + t.Logf("engine stopped") + }() + time.Sleep(time.Second * 1) + sigs <- syscall.SIGINT + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(timeout): + t.Error("Test timed out") + } +} + func TestFixCloseOfChannelAfterCtrlC(t *testing.T) { - // fix https://github.com/cosmtrek/air/issues/294 + // fix https://github.com/air-verse/air/issues/294 dir := initWithBuildFailedCode(t) chdir(t, dir) engine, err := NewEngine("", true) @@ -414,7 +455,7 @@ func TestFixCloseOfChannelAfterCtrlC(t *testing.T) { } func TestFixCloseOfChannelAfterTwoFailedBuild(t *testing.T) { - // fix https://github.com/cosmtrek/air/issues/294 + // fix https://github.com/air-verse/air/issues/294 // happens after two failed builds dir := initWithBuildFailedCode(t) // change dir to tmpDir @@ -499,7 +540,7 @@ func TestRun(t *testing.T) { engine.Stop() time.Sleep(time.Second * 1) assert.False(t, checkPortHaveBeenUsed(port)) - t.Logf("stoped") + t.Logf("stopped") } func checkPortConnectionRefused(port int) bool { @@ -729,14 +770,17 @@ func TestWriteDefaultConfig(t *testing.T) { tmpDir := initTestEnv(t, port) // change dir to tmpDir chdir(t, tmpDir) - writeDefaultConfig() + configName, err := writeDefaultConfig() + if err != nil { + t.Fatal(err) + } // check the file is exist - if _, err := os.Stat(dftTOML); err != nil { + if _, err := os.Stat(configName); err != nil { t.Fatal(err) } // check the file content is right - actual, err := readConfig(dftTOML) + actual, err := readConfig(configName) if err != nil { t.Fatal(err) } @@ -767,7 +811,7 @@ exclude_file = ["main.go"] include_file = ["test/not_a_test.go"] ` - if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { + if err := os.WriteFile(dftTOML, []byte(config), 0o644); err != nil { t.Fatal(err) } engine, err := NewEngine(".air.toml", true) @@ -791,7 +835,10 @@ func TestShouldIncludeGoTestFile(t *testing.T) { tmpDir := initTestEnv(t, port) // change dir to tmpDir chdir(t, tmpDir) - writeDefaultConfig() + _, err := writeDefaultConfig() + if err != nil { + t.Fatal(err) + } // write go test file file, err := os.Create("main_test.go") @@ -806,6 +853,9 @@ func Test(t *testing.T) { t.Log("testing") } `) + if err != nil { + t.Fatal(err) + } // run sed // check the file is exist if _, err := os.Stat(dftTOML); err != nil { @@ -902,7 +952,7 @@ include_ext = ["sh"] include_dir = ["nonexist"] # prevent default "." watch from taking effect include_file = ["main.sh"] ` - if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { + if err := os.WriteFile(dftTOML, []byte(config), 0o644); err != nil { t.Fatal(err) } diff --git a/runner/flag_test.go b/runner/flag_test.go index 6ee095ae..8c14455b 100644 --- a/runner/flag_test.go +++ b/runner/flag_test.go @@ -54,7 +54,7 @@ func TestFlag(t *testing.T) { t.Run(tc.name, func(t *testing.T) { flag := flag.NewFlagSet(t.Name(), flag.ExitOnError) cmdArgs := ParseConfigFlag(flag) - flag.Parse(tc.args) + assert.NoError(t, flag.Parse(tc.args)) assert.Equal(t, tc.expected, *cmdArgs[tc.key].Value) }) } @@ -121,10 +121,10 @@ func TestConfigRuntimeArgs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() - os.Chdir(dir) + assert.NoError(t, os.Chdir(dir)) flag := flag.NewFlagSet(t.Name(), flag.ExitOnError) cmdArgs := ParseConfigFlag(flag) - flag.Parse(tc.args) + _ = flag.Parse(tc.args) cfg, err := InitConfig("") if err != nil { log.Fatal(err) diff --git a/runner/proxy.go b/runner/proxy.go new file mode 100644 index 00000000..6e3bad76 --- /dev/null +++ b/runner/proxy.go @@ -0,0 +1,185 @@ +package runner + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "net/http" + "strconv" + "strings" + "syscall" + "time" +) + +type Reloader interface { + AddSubscriber() *Subscriber + RemoveSubscriber(id int32) + Reload() + Stop() +} + +type Proxy struct { + server *http.Server + client *http.Client + config *cfgProxy + stream Reloader +} + +func NewProxy(cfg *cfgProxy) *Proxy { + p := &Proxy{ + config: cfg, + server: &http.Server{ + Addr: fmt.Sprintf(":%d", cfg.ProxyPort), + }, + client: &http.Client{ + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + }, + stream: NewProxyStream(), + } + return p +} + +func (p *Proxy) Run() { + http.HandleFunc("/", p.proxyHandler) + http.HandleFunc("/internal/reload", p.reloadHandler) + if err := p.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatal(p.Stop()) + } +} + +func (p *Proxy) Reload() { + p.stream.Reload() +} + +func (p *Proxy) injectLiveReload(resp *http.Response) (string, error) { + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(resp.Body); err != nil { + return "", fmt.Errorf("proxy inject: failed to read body from http response") + } + page := buf.String() + + // the script will be injected before the end of the body tag. In case the tag is missing, the injection will be skipped with no error. + body := strings.LastIndex(page, "") + if body == -1 { + return page, nil + } + + script := fmt.Sprintf( + ``, + p.config.ProxyPort, + ) + return page[:body] + script + page[body:], nil +} + +func (p *Proxy) proxyHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + appURL := r.URL + appURL.Scheme = "http" + appURL.Host = fmt.Sprintf("localhost:%d", p.config.AppPort) + + if err := r.ParseForm(); err != nil { + http.Error(w, "proxy handler: bad form", http.StatusInternalServerError) + return + } + var body io.Reader + if len(r.Form) > 0 { + body = strings.NewReader(r.Form.Encode()) + } else { + body = r.Body + } + req, err := http.NewRequest(r.Method, appURL.String(), body) + if err != nil { + http.Error(w, "proxy handler: unable to create request", http.StatusInternalServerError) + return + } + + // Copy the headers from the original request + for name, values := range r.Header { + for _, value := range values { + req.Header.Add(name, value) + } + } + req.Header.Set("X-Forwarded-For", r.RemoteAddr) + + // retry on connection refused error since after a file change air will restart the server and it may take a few milliseconds for the server to be up-and-running. + var resp *http.Response + for i := 0; i < 10; i++ { + resp, err = p.client.Do(req) + if err == nil { + break + } + if !errors.Is(err, syscall.ECONNREFUSED) { + http.Error(w, "proxy handler: unable to reach app", http.StatusInternalServerError) + return + } + time.Sleep(100 * time.Millisecond) + } + defer resp.Body.Close() + + // Copy the headers from the proxy response except Content-Length + for k, vv := range resp.Header { + for _, v := range vv { + if k == "Content-Length" { + continue + } + w.Header().Add(k, v) + } + } + w.WriteHeader(resp.StatusCode) + + if !strings.Contains(resp.Header.Get("Content-Type"), "text/html") { + w.Header().Set("Content-Length", resp.Header.Get("Content-Length")) + if _, err := io.Copy(w, resp.Body); err != nil { + http.Error(w, "proxy handler: failed to forward the response body", http.StatusInternalServerError) + return + } + } else { + page, err := p.injectLiveReload(resp) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Length", strconv.Itoa((len([]byte(page))))) + if _, err := io.WriteString(w, page); err != nil { + http.Error(w, "proxy handler: unable to inject live reload script", http.StatusInternalServerError) + return + } + } +} + +func (p *Proxy) reloadHandler(w http.ResponseWriter, r *http.Request) { + flusher, err := w.(http.Flusher) + if !err { + http.Error(w, "reload handler: streaming unsupported", http.StatusInternalServerError) + return + } + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + sub := p.stream.AddSubscriber() + go func() { + <-r.Context().Done() + p.stream.RemoveSubscriber(sub.id) + }() + + w.WriteHeader(http.StatusOK) + flusher.Flush() + + for range sub.reloadCh { + fmt.Fprintf(w, "data: reload\n\n") + flusher.Flush() + } +} + +func (p *Proxy) Stop() error { + p.stream.Stop() + return p.server.Close() +} diff --git a/runner/proxy_stream.go b/runner/proxy_stream.go new file mode 100644 index 00000000..f3a4bd47 --- /dev/null +++ b/runner/proxy_stream.go @@ -0,0 +1,54 @@ +package runner + +import ( + "sync" + "sync/atomic" +) + +type ProxyStream struct { + mu sync.Mutex + subscribers map[int32]*Subscriber + count atomic.Int32 +} + +type Subscriber struct { + id int32 + reloadCh chan struct{} +} + +func NewProxyStream() *ProxyStream { + return &ProxyStream{subscribers: make(map[int32]*Subscriber)} +} + +func (stream *ProxyStream) Stop() { + for id := range stream.subscribers { + stream.RemoveSubscriber(id) + } + stream.count = atomic.Int32{} +} + +func (stream *ProxyStream) AddSubscriber() *Subscriber { + stream.mu.Lock() + defer stream.mu.Unlock() + stream.count.Add(1) + + sub := &Subscriber{id: stream.count.Load(), reloadCh: make(chan struct{})} + stream.subscribers[stream.count.Load()] = sub + return sub +} + +func (stream *ProxyStream) RemoveSubscriber(id int32) { + stream.mu.Lock() + defer stream.mu.Unlock() + + if _, ok := stream.subscribers[id]; ok { + close(stream.subscribers[id].reloadCh) + delete(stream.subscribers, id) + } +} + +func (stream *ProxyStream) Reload() { + for _, sub := range stream.subscribers { + sub.reloadCh <- struct{}{} + } +} diff --git a/runner/proxy_stream_test.go b/runner/proxy_stream_test.go new file mode 100644 index 00000000..ca1e78cb --- /dev/null +++ b/runner/proxy_stream_test.go @@ -0,0 +1,71 @@ +package runner + +import ( + "sync" + "sync/atomic" + "testing" +) + +func find(s map[int32]*Subscriber, id int32) bool { + for _, sub := range s { + if sub.id == id { + return true + } + } + return false +} + +func TestProxyStream(t *testing.T) { + stream := NewProxyStream() + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func(_ int) { + defer wg.Done() + _ = stream.AddSubscriber() + }(i) + } + wg.Wait() + + if got, exp := len(stream.subscribers), 10; got != exp { + t.Errorf("expect subscribers count to be %d, got %d", exp, got) + } + + doneCh := make(chan struct{}) + go func() { + stream.Reload() + doneCh <- struct{}{} + }() + + var reloadCount atomic.Int32 + for _, sub := range stream.subscribers { + wg.Add(1) + go func(sub *Subscriber) { + defer wg.Done() + <-sub.reloadCh + reloadCount.Add(1) + }(sub) + } + wg.Wait() + <-doneCh + + if got, exp := reloadCount.Load(), int32(10); got != exp { + t.Errorf("expect reloadCount %d, got %d", exp, got) + } + + stream.RemoveSubscriber(2) + if find(stream.subscribers, 2) { + t.Errorf("expected subscriber 2 not to be found") + } + + stream.AddSubscriber() + if !find(stream.subscribers, 11) { + t.Errorf("expected subscriber 11 to be found") + } + + stream.Stop() + if got, exp := len(stream.subscribers), 0; got != exp { + t.Errorf("expected subscribers count to be %d, got %d", exp, got) + } +} diff --git a/runner/proxy_test.go b/runner/proxy_test.go new file mode 100644 index 00000000..a14ec3d6 --- /dev/null +++ b/runner/proxy_test.go @@ -0,0 +1,260 @@ +package runner + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "strings" + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +type reloader struct { + subCh chan struct{} + reloadCh chan struct{} +} + +func (r *reloader) AddSubscriber() *Subscriber { + r.subCh <- struct{}{} + return &Subscriber{reloadCh: r.reloadCh} +} + +func (r *reloader) RemoveSubscriber(_ int32) { + close(r.subCh) +} + +func (r *reloader) Reload() {} +func (r *reloader) Stop() {} + +var proxyPort = 8090 + +func getServerPort(t *testing.T, srv *httptest.Server) int { + mockURL, err := url.Parse(srv.URL) + if err != nil { + t.Fatal(err) + } + port, err := strconv.Atoi(mockURL.Port()) + if err != nil { + t.Fatal(err) + } + return port +} + +func TestProxy_run(t *testing.T) { + _ = os.Unsetenv(airWd) + cfg := &cfgProxy{ + Enabled: true, + ProxyPort: 1111, + AppPort: 2222, + } + proxy := NewProxy(cfg) + if proxy.config == nil { + t.Fatal("config should not be nil") + } + if proxy.server.Addr == "" { + t.Fatal("server address should not be nil") + } + go func() { + proxy.Run() + }() + if err := proxy.Stop(); err != nil { + t.Errorf("failed stopping the proxy: %v", err) + } +} + +func TestProxy_proxyHandler(t *testing.T) { + tests := []struct { + name string + req func() *http.Request + assert func(*http.Request) + }{ + { + name: "get_request_with_headers", + req: func() *http.Request { + req := httptest.NewRequest("GET", fmt.Sprintf("http://localhost:%d", proxyPort), nil) + req.Header.Set("foo", "bar") + return req + }, + assert: func(resp *http.Request) { + assert.Equal(t, "bar", resp.Header.Get("foo")) + }, + }, + { + name: "post_form_request", + req: func() *http.Request { + formData := url.Values{} + formData.Add("foo", "bar") + req := httptest.NewRequest("POST", fmt.Sprintf("http://localhost:%d", proxyPort), strings.NewReader(formData.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return req + }, + assert: func(resp *http.Request) { + assert.NoError(t, resp.ParseForm()) + assert.Equal(t, resp.Form.Get("foo"), "bar") + }, + }, + { + name: "get_request_with_query_string", + req: func() *http.Request { + return httptest.NewRequest("GET", fmt.Sprintf("http://localhost:%d?q=%s", proxyPort, "air"), nil) + }, + assert: func(resp *http.Request) { + q := resp.URL.Query() + assert.Equal(t, q.Encode(), "q=air") + }, + }, + { + name: "put_json_request", + req: func() *http.Request { + body := []byte(`{"foo": "bar"}`) + req := httptest.NewRequest("PUT", fmt.Sprintf("http://localhost:%d/a/b/c", proxyPort), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + return req + }, + assert: func(resp *http.Request) { + type Response struct { + Foo string `json:"foo"` + } + var r Response + assert.NoError(t, json.NewDecoder(resp.Body).Decode(&r)) + assert.Equal(t, resp.URL.Path, "/a/b/c") + assert.Equal(t, r.Foo, "bar") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + tt.assert(r) + })) + defer srv.Close() + srvPort := getServerPort(t, srv) + proxy := NewProxy(&cfgProxy{ + Enabled: true, + ProxyPort: proxyPort, + AppPort: srvPort, + }) + proxy.proxyHandler(httptest.NewRecorder(), tt.req()) + }) + } +} + +func TestProxy_injectLiveReload(t *testing.T) { + tests := []struct { + name string + given *http.Response + expect string + }{ + { + name: "when_no_body_should_not_be_injected", + given: &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + StatusCode: http.StatusOK, + Body: http.NoBody, + }, + expect: "", + }, + { + name: "when_missing_body_should_not_be_injected", + given: &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + StatusCode: http.StatusOK, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: io.NopCloser(strings.NewReader(`

test

`)), + }, + expect: "

test

", + }, + { + name: "when_text_html_and_body_is_present_should_be_injected", + given: &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + StatusCode: http.StatusOK, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: io.NopCloser(strings.NewReader(`

test

`)), + }, + expect: `

test

`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proxy := NewProxy(&cfgProxy{ + Enabled: true, + ProxyPort: 1111, + AppPort: 2222, + }) + if got, _ := proxy.injectLiveReload(tt.given); got != tt.expect { + t.Errorf("expected page %+v, got %v", tt.expect, got) + } + }) + } +} + +func TestProxy_reloadHandler(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + fmt.Fprint(w, "thin air") + })) + srvPort := getServerPort(t, srv) + defer srv.Close() + + reloader := &reloader{subCh: make(chan struct{}), reloadCh: make(chan struct{})} + cfg := &cfgProxy{ + Enabled: true, + ProxyPort: proxyPort, + AppPort: srvPort, + } + proxy := &Proxy{ + config: cfg, + server: &http.Server{ + Addr: fmt.Sprintf("localhost:%d", proxyPort), + }, + stream: reloader, + } + + req := httptest.NewRequest("GET", fmt.Sprintf("http://localhost:%d/internal/reload", proxyPort), nil) + rec := httptest.NewRecorder() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + proxy.reloadHandler(rec, req) + }() + + // wait for subscriber to be added + <-reloader.subCh + + // send a reload event and wait for http response + reloader.reloadCh <- struct{}{} + close(reloader.reloadCh) + wg.Wait() + + if !rec.Flushed { + t.Errorf("request should have been flushed") + } + + resp := rec.Result() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("reading body: %v", err) + } + if got, exp := string(bodyBytes), "data: reload\n\n"; got != exp { + t.Errorf("expected %q but got %q", exp, got) + } +} diff --git a/runner/util.go b/runner/util.go index 9f982dbb..e0b03b85 100644 --- a/runner/util.go +++ b/runner/util.go @@ -241,7 +241,6 @@ func adaptToVariousPlatforms(c *Config) { if 0 < len(c.Build.FullBin) { if !strings.HasSuffix(c.Build.FullBin, extName) { - c.Build.FullBin += extName } if !strings.HasPrefix(c.Build.FullBin, runName) { diff --git a/runner/util_linux.go b/runner/util_linux.go index 5df91e84..658671b3 100644 --- a/runner/util_linux.go +++ b/runner/util_linux.go @@ -17,7 +17,7 @@ func (e *Engine) killCmd(cmd *exec.Cmd) (pid int, err error) { if err = syscall.Kill(-pid, syscall.SIGINT); err != nil { return } - time.Sleep(e.config.Build.KillDelay) + time.Sleep(e.config.killDelay()) } // https://stackoverflow.com/questions/22470193/why-wont-go-kill-a-child-process-correctly diff --git a/runner/util_test.go b/runner/util_test.go index eb16321a..275d5b78 100644 --- a/runner/util_test.go +++ b/runner/util_test.go @@ -3,7 +3,6 @@ package runner import ( "errors" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -206,26 +205,26 @@ func Test_killCmd_SendInterrupt_false(t *testing.T) { pid int cmd *exec.Cmd }{pid: pid, cmd: cmd} - cmd.Wait() + if err := cmd.Wait(); err != nil { + t.Logf("failed to wait command: %v", err) + } t.Logf("wait finished") }() resp := <-startChan t.Logf("process started. checking pid %v", resp.pid) time.Sleep(2 * time.Second) t.Logf("%v", resp.cmd.Process.Pid) - pid, err := e.killCmd(resp.cmd) - if err != nil { - t.Fatalf("failed to kill command: %v", err) - } + pid, _ := e.killCmd(resp.cmd) t.Logf("%v was been killed", pid) // check processes were being killed // read pids from file - bytesRead, _ := ioutil.ReadFile("pid") + bytesRead, err := os.ReadFile("pid") + assert.NoError(t, err) lines := strings.Split(string(bytesRead), "\n") for _, line := range lines { _, err := strconv.Atoi(line) if err != nil { - t.Logf("failed to covert str to int %v", err) + t.Logf("failed to convert str to int %v", err) continue } _, err = exec.Command("ps", "-p", line, "-o", "comm= ").Output() @@ -238,6 +237,7 @@ func Test_killCmd_SendInterrupt_false(t *testing.T) { func TestGetStructureFieldTagMap(t *testing.T) { c := Config{} tagMap := flatConfig(c) + assert.NotEmpty(t, tagMap) for _, i2 := range tagMap { fmt.Printf("%v\n", i2.fieldPath) } @@ -279,7 +279,7 @@ func TestCheckIncludeFile(t *testing.T) { e := Engine{ config: &Config{ Build: cfgBuild{ - IncludeFile: []string{"main.go"}, + IncludeFile: []string{"main.go"}, }, }, } diff --git a/runner/util_unix.go b/runner/util_unix.go index 4590434f..afd47c89 100644 --- a/runner/util_unix.go +++ b/runner/util_unix.go @@ -18,7 +18,7 @@ func (e *Engine) killCmd(cmd *exec.Cmd) (pid int, err error) { if err = syscall.Kill(-pid, syscall.SIGINT); err != nil { return } - time.Sleep(e.config.Build.KillDelay) + time.Sleep(e.config.killDelay()) } // https://stackoverflow.com/questions/22470193/why-wont-go-kill-a-child-process-correctly err = syscall.Kill(-pid, syscall.SIGKILL) diff --git a/smoke_test/smoke_test.py b/smoke_test/smoke_test.py index e52ccb44..74f34918 100644 --- a/smoke_test/smoke_test.py +++ b/smoke_test/smoke_test.py @@ -5,17 +5,17 @@ os.chdir(os.getcwd() + "\check_rebuild") print(os.getcwd()) -chlid = PopenSpawn("air") -chlid.expect +child = PopenSpawn("air") +child.expect -a = chlid.expect("running", timeout=300) +a = child.expect("running", timeout=300) if a == 0: with open("main.go", "a") as f: f.write("\n\n") else: exit(0) -a = chlid.expect("running", timeout=300) +a = child.expect("running", timeout=300) if a == 0: print("::set-output name=value::PASS") else: diff --git a/version.go b/version.go index a969d1b6..e8904dc4 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,6 @@ package main -var airVersion string -var goVersion string +var ( + airVersion string + goVersion string +)