mirror of
https://github.com/foomo/ownbrew.git
synced 2025-10-16 12:35:38 +00:00
feat: initial commit
This commit is contained in:
commit
5263d389a2
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{yaml,yml,md,mdx}]
|
||||
indent_style = space
|
||||
25
.github/dependabot.yml
vendored
Normal file
25
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
day: 'sunday'
|
||||
interval: 'weekly'
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- '*'
|
||||
|
||||
- package-ecosystem: 'gomod'
|
||||
directory: '/'
|
||||
schedule:
|
||||
day: 'sunday'
|
||||
interval: 'weekly'
|
||||
groups:
|
||||
gomod-security:
|
||||
applies-to: security-updates
|
||||
patterns: ['*']
|
||||
gomod-update:
|
||||
applies-to: version-updates
|
||||
patterns: ['*']
|
||||
36
.github/workflows/release.yml
vendored
Normal file
36
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Release Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
|
||||
- id: app_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||
38
.github/workflows/test.yml
vendored
Normal file
38
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Test Branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- uses: gotesttools/gotestfmt-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
- uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
file: coverage.out
|
||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
.*
|
||||
*.zip
|
||||
*.tar
|
||||
*.out
|
||||
*.log
|
||||
/bin/
|
||||
/tmp/
|
||||
|
||||
## Git
|
||||
!.gitkeep
|
||||
!.gitignore
|
||||
|
||||
## GitHub
|
||||
!.github/
|
||||
|
||||
## Editorconfig
|
||||
!.editorconfig
|
||||
|
||||
## Husky
|
||||
!.husky/
|
||||
!.husky.yaml
|
||||
|
||||
## Golang
|
||||
!.golangci.yml
|
||||
!.goreleaser.yml
|
||||
193
.golangci.yml
Normal file
193
.golangci.yml
Normal file
@ -0,0 +1,193 @@
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- 'bin'
|
||||
- 'tmp'
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- forcetypeassert
|
||||
|
||||
linters-settings:
|
||||
# https://golangci-lint.run/usage/linters/#misspell
|
||||
misspell:
|
||||
mode: restricted
|
||||
# https://golangci-lint.run/usage/linters/#asasalint
|
||||
asasalint:
|
||||
ignore-test: true
|
||||
# https://golangci-lint.run/usage/linters/#exhaustive
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
# https://golangci-lint.run/usage/linters/#predeclared
|
||||
predeclared:
|
||||
ignore: "new,error"
|
||||
# https://golangci-lint.run/usage/linters/#gocritic
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- commentFormatting
|
||||
# https://golangci-lint.run/usage/linters/#testifylint
|
||||
testifylint:
|
||||
disable:
|
||||
- float-compare
|
||||
# https://golangci-lint.run/usage/linters/#gosec
|
||||
gosec:
|
||||
confidence: medium
|
||||
excludes:
|
||||
- G204
|
||||
# https://golangci-lint.run/usage/linters/#importas
|
||||
importas:
|
||||
no-unaliased: true
|
||||
# https://golangci-lint.run/usage/linters/#gomoddirectives
|
||||
gomoddirectives:
|
||||
replace-local: true
|
||||
# https://golangci-lint.run/usage/linters/#revive
|
||||
revive:
|
||||
ignore-generated-header: true
|
||||
enable-all-rules: true
|
||||
rules:
|
||||
- name: line-length-limit
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: add-constant
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: flag-parameter
|
||||
disabled: true
|
||||
- name: unused-receiver
|
||||
disabled: true
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: max-control-nesting
|
||||
disabled: true
|
||||
- name: comment-spacings
|
||||
disabled: true
|
||||
- name: struct-tag
|
||||
arguments:
|
||||
- "json,inline"
|
||||
- "yaml,squash"
|
||||
- name: unhandled-error
|
||||
arguments:
|
||||
- "fmt.Println"
|
||||
- "viper.BindPFlag"
|
||||
- "strings.Builder.WriteString"
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
## Enabled by default linters:
|
||||
- errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||
- gosimple # (megacheck) Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false]
|
||||
- govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false]
|
||||
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||
- staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false]
|
||||
- unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||
|
||||
## Disabled by your configuration linters:
|
||||
- asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false]
|
||||
- asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false]
|
||||
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false]
|
||||
- contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false]
|
||||
- copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false]
|
||||
- decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false]
|
||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false]
|
||||
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
|
||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||
#- forbidigo # Forbids identifiers [fast: false, auto-fix: false]
|
||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||
- gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false]
|
||||
- gochecksumtype # Run exhaustiveness checks on Go "sum types" [fast: false, auto-fix: false]
|
||||
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: true]
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: true]
|
||||
- goimports # Check import statements are formatted according to the 'goimport' command. Reformat imports in autofix mode. [fast: true, auto-fix: true]
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end. [fast: true, auto-fix: false]
|
||||
- gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false]
|
||||
- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false]
|
||||
- grouper # Analyze expression groups. [fast: true, auto-fix: false]
|
||||
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
- inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false]
|
||||
- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false]
|
||||
- loggercheck # (logrlint) Checks key value pairs for common logger libraries (kitlog,klog,logr,zap). [fast: false, auto-fix: false]
|
||||
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
|
||||
- misspell # Finds commonly misspelled English words [fast: true, auto-fix: true]
|
||||
- mirror # reports wrong mirror patterns of bytes/strings usage [fast: false, auto-fix: true]
|
||||
- musttag # enforce field tags in (un)marshaled structs [fast: false, auto-fix: false]
|
||||
- nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). [fast: true, auto-fix: false]
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
|
||||
- noctx # Finds sending http request without context.Context [fast: false, auto-fix: false]
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: true]
|
||||
- nonamedreturns # Reports all named returns [fast: false, auto-fix: false]
|
||||
- nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false]
|
||||
- paralleltest # Detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false]
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
|
||||
- promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false]
|
||||
- reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false]
|
||||
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||
- rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false]
|
||||
- spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false]
|
||||
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. [fast: false, auto-fix: false]
|
||||
- stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false]
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
|
||||
- testableexamples # linter checks if examples are testable (have an expected output) [fast: true, auto-fix: false]
|
||||
- testifylint # Checks usage of github.com/stretchr/testify. [fast: false, auto-fix: false]
|
||||
- testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false]
|
||||
- thelper # thelper detects tests helpers which is not start with t.Helper() method. [fast: false, auto-fix: false]
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false]
|
||||
- unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false]
|
||||
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false]
|
||||
- wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false]
|
||||
- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true]
|
||||
- canonicalheader # Checks whether net/http.Header uses canonical header [fast: false, auto-fix: false]
|
||||
- fatcontext #Detects nested contexts in loops [fast: false, auto-fix: false]
|
||||
|
||||
## Don't enable
|
||||
#- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
|
||||
#- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
||||
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
|
||||
#- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false]
|
||||
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||
#- err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false]
|
||||
#- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false]
|
||||
#- gci # Gci controls Go package import order and makes it always deterministic. [fast: true, auto-fix: true]
|
||||
#- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false]
|
||||
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
#- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||
#- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||
#- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||
#- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||
#- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||
#- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false]
|
||||
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
#- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false]
|
||||
#- lll # Reports long lines [fast: true, auto-fix: false]
|
||||
#- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
|
||||
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||
#- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
|
||||
#- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false]
|
||||
#- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
|
||||
#- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true]
|
||||
#- sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false]
|
||||
#- tagalign # check that struct tags are well aligned [fast: true, auto-fix: true]
|
||||
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
|
||||
#- unparam # Reports unused function parameters [fast: false, auto-fix: false]
|
||||
#- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
|
||||
#- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||
#- wsl # add or remove empty lines [fast: true, auto-fix: false]
|
||||
#- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false]
|
||||
40
.goreleaser.yml
Normal file
40
.goreleaser.yml
Normal file
@ -0,0 +1,40 @@
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: ownbrew
|
||||
main: ./main.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- '7'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X github.com/foomo/ownbrew/cmd.version={{.Version}}
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
changelog:
|
||||
use: github-native
|
||||
|
||||
brews:
|
||||
- repository:
|
||||
owner: foomo
|
||||
name: homebrew-tap
|
||||
caveats: "ownbrew --help"
|
||||
homepage: "https://github.com/foomo/ownbrew"
|
||||
description: "Your local project package manager"
|
||||
15
.husky.yaml
Normal file
15
.husky.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
hooks:
|
||||
pre-commit:
|
||||
- golangci-lint run --fast
|
||||
- husky lint-staged
|
||||
commit-msg:
|
||||
# only execute if not in a merge
|
||||
- if [[ -z $(git rev-parse -q --verify MERGE_HEAD) ]]; then husky lint-commit; fi
|
||||
|
||||
lint-staged:
|
||||
'*.go':
|
||||
- goimports -l -w
|
||||
|
||||
lint-commit:
|
||||
types: '^(feat|fix|build|chore|docs|perf|refactor|revert|style|test|wip)$'
|
||||
header: '^(?P<type>\w+)(\((?P<scope>[\w/.-]+)\))?(?P<breaking>!)?:( +)?(?P<header>.+)'
|
||||
3
.husky/applypatch-msg
Executable file
3
.husky/applypatch-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/commit-msg
Executable file
3
.husky/commit-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/fsmonitor-watchman
Executable file
3
.husky/fsmonitor-watchman
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/post-update
Executable file
3
.husky/post-update
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-applypatch
Executable file
3
.husky/pre-applypatch
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-commit
Executable file
3
.husky/pre-commit
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-merge-commit
Executable file
3
.husky/pre-merge-commit
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-push
Executable file
3
.husky/pre-push
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-rebase
Executable file
3
.husky/pre-rebase
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/pre-receive
Executable file
3
.husky/pre-receive
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/prepare-commit-msg
Executable file
3
.husky/prepare-commit-msg
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/push-to-checkout
Executable file
3
.husky/push-to-checkout
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/sendemail-validate
Executable file
3
.husky/sendemail-validate
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
3
.husky/update
Executable file
3
.husky/update
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
husky hook $(basename "$0") $*
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
info@bestbytes.de.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Foomo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
109
Makefile
Normal file
109
Makefile
Normal file
@ -0,0 +1,109 @@
|
||||
.DEFAULT_GOAL:=help
|
||||
-include .makerc
|
||||
|
||||
# --- Config -----------------------------------------------------------------
|
||||
|
||||
export PATH := bin:$(PATH)
|
||||
|
||||
# --- Targets -----------------------------------------------------------------
|
||||
|
||||
# This allows us to accept extra arguments
|
||||
%: .husky
|
||||
@:
|
||||
|
||||
.PHONY: .husky
|
||||
# Configure git hooks for husky
|
||||
.husky:
|
||||
@if ! command -v husky &> /dev/null; then \
|
||||
echo "ERROR: missing executable 'husky', please run:"; \
|
||||
echo "\n$ go install github.com/go-courier/husky/cmd/husky@latest\n"; \
|
||||
fi
|
||||
@git config core.hooksPath .husky
|
||||
|
||||
## === Tasks ===
|
||||
|
||||
.PHONY: brew
|
||||
## Install project binaries
|
||||
brew:
|
||||
@ownbrew install
|
||||
|
||||
.PHONY: doc
|
||||
## Open go docs
|
||||
doc:
|
||||
@open "http://localhost:6060/pkg/github.com/foomo/ownbrew/"
|
||||
@godoc -http=localhost:6060 -play
|
||||
|
||||
.PHONY: test
|
||||
## Run tests
|
||||
test:
|
||||
@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
|
||||
.PHONY: lint
|
||||
## Run linter
|
||||
lint:
|
||||
@golangci-lint run
|
||||
|
||||
.PHONY: lint.fix
|
||||
## Fix lint violations
|
||||
lint.fix:
|
||||
@golangci-lint run --fix
|
||||
|
||||
.PHONY: tidy
|
||||
## Run go mod tidy
|
||||
tidy:
|
||||
@go mod tidy
|
||||
|
||||
.PHONY: outdated
|
||||
## Show outdated direct dependencies
|
||||
outdated:
|
||||
@go list -u -m -json all | go-mod-outdated -update -direct
|
||||
|
||||
.PHONY: build
|
||||
## Build binary
|
||||
build:
|
||||
@mkdir -p bin
|
||||
@go build -o bin/ownbrew main.go
|
||||
|
||||
.PHONY: install
|
||||
## Install binary
|
||||
install:
|
||||
@go build -o ${GOPATH}/bin/ownbrew main.go
|
||||
|
||||
.PHONY: install.debug
|
||||
## Install debug binary
|
||||
install.debug:
|
||||
@go build -gcflags "all=-N -l" -o ${GOPATH}/bin/ownbrew main.go
|
||||
|
||||
## === Utils ===
|
||||
|
||||
## Show help text
|
||||
help:
|
||||
@awk '{ \
|
||||
if ($$0 ~ /^.PHONY: [a-zA-Z\-\_0-9]+$$/) { \
|
||||
helpCommand = substr($$0, index($$0, ":") + 2); \
|
||||
if (helpMessage) { \
|
||||
printf "\033[36m%-23s\033[0m %s\n", \
|
||||
helpCommand, helpMessage; \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
} else if ($$0 ~ /^[a-zA-Z\-\_0-9.]+:/) { \
|
||||
helpCommand = substr($$0, 0, index($$0, ":")); \
|
||||
if (helpMessage) { \
|
||||
printf "\033[36m%-23s\033[0m %s\n", \
|
||||
helpCommand, helpMessage"\n"; \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
} else if ($$0 ~ /^##/) { \
|
||||
if (helpMessage) { \
|
||||
helpMessage = helpMessage"\n "substr($$0, 3); \
|
||||
} else { \
|
||||
helpMessage = substr($$0, 3); \
|
||||
} \
|
||||
} else { \
|
||||
if (helpMessage) { \
|
||||
print "\n "helpMessage"\n" \
|
||||
} \
|
||||
helpMessage = ""; \
|
||||
} \
|
||||
}' \
|
||||
$(MAKEFILE_LIST)
|
||||
361
README.md
Normal file
361
README.md
Normal file
@ -0,0 +1,361 @@
|
||||
# Sesamy CLI
|
||||
|
||||
[](https://github.com/foomo/sesamy-cli/actions/workflows/test.yml)
|
||||
[](https://goreportcard.com/report/github.com/foomo/sesamy-cli)
|
||||
[](https://godoc.org/github.com/foomo/sesamy-cli)
|
||||
[](https://github.com/foomo/sesamy-cli/actions)
|
||||
|
||||
> CLI to keep you sane while working with GTM.
|
||||
|
||||
## Installing
|
||||
|
||||
Install the latest release of the cli:
|
||||
|
||||
````bash
|
||||
$ brew update
|
||||
$ brew install foomo/tap/sesamy-cli
|
||||
````
|
||||
|
||||
## Usage
|
||||
|
||||
Add a `sesamy.yaml` configuration
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/foomo/sesamy-cli/v0.4.1/sesamy.yaml
|
||||
version: '1.0'
|
||||
|
||||
# Whether to redact the visitor ip
|
||||
redactVisitorIp: true
|
||||
|
||||
# --- Google API settings
|
||||
googleApi:
|
||||
# Single line Service Account credentials
|
||||
credentials: '{...\\n...\\n...}'
|
||||
# Path to the Service Account credentials json file
|
||||
credentialsFile: google_service_account_creds.json
|
||||
# Current API request quota (send a request to increase the quota)
|
||||
requestQuota: 15
|
||||
|
||||
# --- Google Tag Manager settings
|
||||
googleTagManager:
|
||||
# The account id
|
||||
accountId: 6099238525
|
||||
# Web container settings
|
||||
webContainer:
|
||||
# The container tag id
|
||||
tagId: GTM-57BHX34G
|
||||
# The container id
|
||||
containerId: 175355532
|
||||
# The workspace id that should be used by the api
|
||||
workspaceId: 23
|
||||
# Server container settings
|
||||
serverContainer:
|
||||
# The container tag id
|
||||
tagId: GTM-5NWPR4QW
|
||||
# The container id
|
||||
containerId: 175348980
|
||||
# The workspace id that should be used by the api
|
||||
workspaceId: 10
|
||||
|
||||
# --- Google Tag settings
|
||||
googleTag:
|
||||
# A tag ID is an identifier that you put on your page to load a given Google tag
|
||||
tagId: G-PZ5ELRCR31
|
||||
# Whether a page_view should be sent on initial load
|
||||
sendPageView: true
|
||||
# Enable debug mode for all user devices
|
||||
debugMode: false
|
||||
# Google Tag Manager web container settings
|
||||
webContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- PageView
|
||||
- SelectItem
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- PageView
|
||||
- SelectItem
|
||||
# Google Tag Manager web container settings
|
||||
typeScript:
|
||||
# Target directory for generate files
|
||||
outputPath: path/to/target
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
## GA4 Automatically collected events
|
||||
## https://support.google.com/analytics/answer/9234069
|
||||
- Click
|
||||
- FileDownload
|
||||
- FirstVisit
|
||||
- FormStart
|
||||
- FormSubmit
|
||||
- PageView
|
||||
- Scroll
|
||||
- UserEngagement
|
||||
- VideoComplete
|
||||
- VideoProgress
|
||||
- VideoStart
|
||||
- ViewSearchResults
|
||||
## Recommended events
|
||||
## https://developers.google.com/tag-platform/gtagjs/reference/events
|
||||
- AdImpression
|
||||
- AddPaymentInfo
|
||||
- AddShippingInfo
|
||||
- AddToCart
|
||||
- AddToWishlist
|
||||
- BeginCheckout
|
||||
- CampaignDetails
|
||||
- CloseConvertLead
|
||||
- CloseUnconvertLead
|
||||
- DisqualifyLead
|
||||
- EarnVirtualMoney
|
||||
- Exception
|
||||
- GenerateLead
|
||||
- JoinGroup
|
||||
- LevelEnd
|
||||
- LevelStart
|
||||
- LevelUp
|
||||
- Login
|
||||
- PostScore
|
||||
- Purchase
|
||||
- QualifyLead
|
||||
- Refund
|
||||
- RemoveFromCart
|
||||
- ScreenView
|
||||
- Search
|
||||
- SelectContent
|
||||
- SelectItem
|
||||
- SelectPromotion
|
||||
- SessionStart
|
||||
- Share
|
||||
- SignUp
|
||||
- SpendVirtualCurrency
|
||||
- TutorialBegin
|
||||
- TutorialComplete
|
||||
- UnlockAchievement
|
||||
- ViewCart
|
||||
- ViewItem
|
||||
- ViewItemList
|
||||
- ViewPromotion
|
||||
- WorkingLead
|
||||
|
||||
|
||||
# --- Google Analytics settings
|
||||
googleAnalytics:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Google GTag.js settings
|
||||
googleGTag:
|
||||
# Provision custom client
|
||||
enabled: true
|
||||
# Client priority
|
||||
priority: 10
|
||||
# Patch ecommerce items
|
||||
ecommerceItems: true
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: analytics_storage
|
||||
# Google Tag Manager web container settings
|
||||
webContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- PageView
|
||||
- SelectItem
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- PageView
|
||||
- SelectItem
|
||||
|
||||
# --- Google Ads
|
||||
googleAds:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Google Ads Conversion Tracking ID
|
||||
conversionId: ''
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: ad_storage
|
||||
# Google Ads Remarketing settings
|
||||
remarketing:
|
||||
# Enable Google Ads Remarketing
|
||||
enabled: true
|
||||
# Enable conversion linking
|
||||
enableConversionLinker: true
|
||||
# Google Ads Conversion settings
|
||||
conversion:
|
||||
# Enable Google Ads Conversion
|
||||
enabled: true
|
||||
# Google Ads Conversion Tracking Label
|
||||
conversionLabel: ''
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- AddToCart
|
||||
- Purchase
|
||||
|
||||
# --- Conversion Linker settings
|
||||
conversionLinker:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: ad_storage
|
||||
|
||||
# --- Umami settings
|
||||
umami:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Enter an optional fixed domain to override event data
|
||||
domain: your-domain.com
|
||||
# Paste ID for your website from the Umami settings
|
||||
websiteId: ''
|
||||
# Endpoint url of the umami api
|
||||
endpointUrl: https://umami.your-domain.com
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: analytics_storage
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- PageView
|
||||
- SelectItem
|
||||
|
||||
# --- Facebook
|
||||
# https://developers.facebook.com/docs/marketing-api/conversions-api/guides/gtm-server-side
|
||||
facebook:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Facebook pixel id
|
||||
pixelId: ''
|
||||
# To use the Conversions API, you need an access token.
|
||||
apiAccessToken: ''
|
||||
# Code used to verify that your server events are received correctly by Conversions API
|
||||
testEventToken: ''
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: ad_storage
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- AddPaymentInfo
|
||||
- AddToCart
|
||||
- AddToWishlist
|
||||
- PageView
|
||||
- Purchase
|
||||
- Search
|
||||
- BeginCheckout
|
||||
- GenerateLead
|
||||
- ViewItem
|
||||
|
||||
# --- Emarsys
|
||||
emarsys:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Emarsys merchant id
|
||||
merchantId: ''
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: analytics_storage
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- Purchase
|
||||
- ViewItem
|
||||
- ViewItemList
|
||||
|
||||
# --- Tracify
|
||||
tracify:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Tracify token
|
||||
token: ''
|
||||
# Tracify customer site id
|
||||
customerSiteId: ''
|
||||
# Google Consent settings
|
||||
googleConsent:
|
||||
# Enable consent mode
|
||||
enabled: true
|
||||
# Consent mode name
|
||||
mode: analytics_storage
|
||||
# Google Tag Manager server container settings
|
||||
serverContainer:
|
||||
# Contemplate package config for generated events
|
||||
packages:
|
||||
- path: github.com/foomo/sesamy-go/pkg/event
|
||||
types:
|
||||
- AddToCart
|
||||
- PageView
|
||||
- ViewItem
|
||||
- Purchase
|
||||
|
||||
# --- Cookiebot CMP
|
||||
cookiebot:
|
||||
# Enable provider
|
||||
enabled: true
|
||||
# Name of the manually installed Cookiebot CMP tag template
|
||||
# "https://tagmanager.google.com/gallery/#/owners/cybotcorp/templates/gtm-templates-cookiebot-cmp
|
||||
templateName: Cookiebot CMP
|
||||
# Cookiebot id
|
||||
cookiebotId: ''
|
||||
# CDN Region (eu, com)
|
||||
cdnRegion: eu
|
||||
# Enable URL passthrough
|
||||
urlPassthrough: false
|
||||
# Enable advertiser consent mode
|
||||
advertiserConsentModeEnabled: false
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
You might need to increase your Google Tag Manager API quotas, since they are limited to 15 r/m by default.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Make a pull request...
|
||||
|
||||
## License
|
||||
|
||||
Distributed under MIT License, please see license file within the code for more details.
|
||||
35
cmd/config.go
Normal file
35
cmd/config.go
Normal file
@ -0,0 +1,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
pkgcmd "github.com/foomo/ownbrew/pkg/cmd"
|
||||
"github.com/foomo/ownbrew/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewConfig(root *cobra.Command) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Print config",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
l := pkgcmd.Logger()
|
||||
|
||||
cfg, err := pkgcmd.ReadConfig(l, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(util.Highlight(string(out)))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
root.AddCommand(cmd)
|
||||
}
|
||||
52
cmd/install.go
Normal file
52
cmd/install.go
Normal file
@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
pkgcmd "github.com/foomo/ownbrew/pkg/cmd"
|
||||
"github.com/foomo/ownbrew/pkg/ownbrew"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewInstall represents the install command
|
||||
func NewInstall(root *cobra.Command) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install dependencies",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
l := pkgcmd.Logger()
|
||||
|
||||
cfg, err := pkgcmd.ReadConfig(l, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dry, err := cmd.Flags().GetBool("dry")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
brew, err := ownbrew.New(l,
|
||||
ownbrew.WithDry(dry),
|
||||
ownbrew.WithBinDir(cfg.BinDir),
|
||||
ownbrew.WithTapDir(cfg.TapDir),
|
||||
ownbrew.WithTempDir(cfg.TempDir),
|
||||
ownbrew.WithCellarDir(cfg.CellarDir),
|
||||
ownbrew.WithPackages(cfg.Packages...),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return brew.Install(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("dry", false, "dry run")
|
||||
|
||||
root.AddCommand(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
41
cmd/root.go
Normal file
41
cmd/root.go
Normal file
@ -0,0 +1,41 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
pkgcmd "github.com/foomo/ownbrew/pkg/cmd"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var root *cobra.Command
|
||||
|
||||
func init() {
|
||||
root = NewRoot()
|
||||
NewVersion(root)
|
||||
NewInstall(root)
|
||||
NewConfig(root)
|
||||
cobra.OnInitialize(pkgcmd.InitConfig)
|
||||
}
|
||||
|
||||
// NewRoot represents the base command when called without any subcommands
|
||||
func NewRoot() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "ownbrew",
|
||||
Short: "Your local project package manager",
|
||||
}
|
||||
cmd.PersistentFlags().BoolP("verbose", "v", false, "output debug information")
|
||||
_ = viper.BindPFlag("verbose", cmd.PersistentFlags().Lookup("verbose"))
|
||||
|
||||
cmd.PersistentFlags().StringP("config", "c", "ownbrew.yaml", "config file (default is ownbrew.yaml)")
|
||||
_ = viper.BindPFlag("config", cmd.PersistentFlags().Lookup("config"))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := root.Execute(); err != nil {
|
||||
os.Exit(1) //nolint:revive
|
||||
}
|
||||
}
|
||||
21
cmd/version.go
Normal file
21
cmd/version.go
Normal file
@ -0,0 +1,21 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var version = "latest"
|
||||
|
||||
func NewVersion(root *cobra.Command) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(version)
|
||||
},
|
||||
}
|
||||
|
||||
root.AddCommand(cmd)
|
||||
}
|
||||
53
go.mod
Normal file
53
go.mod
Normal file
@ -0,0 +1,53 @@
|
||||
module github.com/foomo/ownbrew
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/foomo/go v0.0.3
|
||||
github.com/invopop/jsonschema v0.12.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pterm/pterm v0.12.79
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.2.0 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
192
go.sum
Normal file
192
go.sum
Normal file
@ -0,0 +1,192 @@
|
||||
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
|
||||
atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=
|
||||
atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=
|
||||
atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
|
||||
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
|
||||
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
|
||||
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
|
||||
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
|
||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
|
||||
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
|
||||
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
|
||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk=
|
||||
github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio=
|
||||
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
|
||||
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
||||
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||
github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4=
|
||||
github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
|
||||
7
main.go
Normal file
7
main.go
Normal file
@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "github.com/foomo/ownbrew/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
40
main_test.go
Normal file
40
main_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
testingx "github.com/foomo/go/testing"
|
||||
tagx "github.com/foomo/go/testing/tag"
|
||||
"github.com/foomo/ownbrew/pkg/config"
|
||||
"github.com/invopop/jsonschema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
testingx.Tags(t, tagx.Short)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
reflector := new(jsonschema.Reflector)
|
||||
require.NoError(t, reflector.AddGoComments("github.com/foomo/ownbrew", "./"))
|
||||
schema := reflector.Reflect(&config.Config{})
|
||||
actual, err := json.MarshalIndent(schema, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
filename := path.Join(cwd, "ownbrew.schema.json")
|
||||
expected, err := os.ReadFile(filename)
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if !assert.Equal(t, string(expected), string(actual)) {
|
||||
require.NoError(t, os.WriteFile(filename, actual, 0600))
|
||||
}
|
||||
}
|
||||
84
ownbrew.schema.json
Normal file
84
ownbrew.schema.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://github.com/foomo/ownbrew/pkg/config/config",
|
||||
"$ref": "#/$defs/Config",
|
||||
"$defs": {
|
||||
"Config": {
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Config version"
|
||||
},
|
||||
"binDir": {
|
||||
"type": "string",
|
||||
"description": "Path to the executable symlinks"
|
||||
},
|
||||
"tapDir": {
|
||||
"type": "string",
|
||||
"description": "Path to your project taps"
|
||||
},
|
||||
"tempDir": {
|
||||
"type": "string",
|
||||
"description": "Path for the downloaded sources"
|
||||
},
|
||||
"cellarDir": {
|
||||
"type": "string",
|
||||
"description": "Path to the versioned executables"
|
||||
},
|
||||
"packages": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Package"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "List of packages that should be installed"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"version",
|
||||
"binDir",
|
||||
"tapDir",
|
||||
"tempDir",
|
||||
"cellarDir"
|
||||
],
|
||||
"description": "Ownbrew configuration"
|
||||
},
|
||||
"Package": {
|
||||
"properties": {
|
||||
"tap": {
|
||||
"type": "string",
|
||||
"description": "Name of the tap"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the package"
|
||||
},
|
||||
"names": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Names of the packages"
|
||||
},
|
||||
"args": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Additional command args"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version of the package to install"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tap",
|
||||
"version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
16
ownbrew.yaml
Normal file
16
ownbrew.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
# yaml-language-server: $schema=ownbrew.schema.json
|
||||
version: '1.0'
|
||||
|
||||
binDir: "bin"
|
||||
tapDir: ".ownbrew"
|
||||
tempDir: "tmp/ownbrew"
|
||||
cellarDir: "tmp/bin"
|
||||
packages:
|
||||
## https://github.com/golangci/golangci-lint/releases
|
||||
- name: golangci-lint
|
||||
tap: foomo/tap/golangci/golangci-lint
|
||||
version: 1.61.0
|
||||
## https://github.com/go-courier/husky/releases
|
||||
- name: husky
|
||||
tap: foomo/tap/go-courier/husky
|
||||
version: 1.8.1
|
||||
65
pkg/cmd/config.go
Normal file
65
pkg/cmd/config.go
Normal file
@ -0,0 +1,65 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/foomo/ownbrew/pkg/config"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// InitConfig reads in config file and ENV variables if set.
|
||||
func InitConfig() {
|
||||
viper.SetConfigType("yaml")
|
||||
filename := viper.GetString("config")
|
||||
|
||||
if filename == "-" {
|
||||
return
|
||||
}
|
||||
if filename != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(filename)
|
||||
return
|
||||
}
|
||||
// Search config in home directory with name ".sesamy" (without extension).
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("ownbrew")
|
||||
}
|
||||
|
||||
func ReadConfig(l *slog.Logger, cmd *cobra.Command) (*config.Config, error) {
|
||||
filename := viper.GetString("config")
|
||||
|
||||
if filename == "-" {
|
||||
l.Debug("using config from stdin")
|
||||
b, err := io.ReadAll(cmd.InOrStdin())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := viper.ReadConfig(bytes.NewBuffer(b)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
l.Debug("using config file", "filename", viper.ConfigFileUsed())
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// l.Debug("config", l.ArgsFromMap(viper.AllSettings()))
|
||||
|
||||
var cfg *config.Config
|
||||
if err := viper.Unmarshal(&cfg, func(decoderConfig *mapstructure.DecoderConfig) {
|
||||
decoderConfig.TagName = "yaml"
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal config")
|
||||
}
|
||||
|
||||
if cfg.Version != config.Version {
|
||||
return nil, errors.New("missing or invalid config version: " + cfg.Version + " != '" + config.Version + "'")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
23
pkg/cmd/logger.go
Normal file
23
pkg/cmd/logger.go
Normal file
@ -0,0 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func Logger() *slog.Logger {
|
||||
verbose := viper.GetBool("verbose")
|
||||
|
||||
plogger := pterm.DefaultLogger.WithTime(false)
|
||||
if verbose {
|
||||
plogger = plogger.WithLevel(pterm.LogLevelTrace).WithCaller(true)
|
||||
}
|
||||
|
||||
// Create a new slog handler with the default PTerm logger
|
||||
handler := pterm.NewSlogHandler(plogger)
|
||||
|
||||
// Create a new slog logger with the handler
|
||||
return slog.New(handler)
|
||||
}
|
||||
17
pkg/config/config.go
Normal file
17
pkg/config/config.go
Normal file
@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
// Ownbrew configuration
|
||||
type Config struct {
|
||||
// Config version
|
||||
Version string `json:"version" yaml:"version"`
|
||||
// Path to the executable symlinks
|
||||
BinDir string `json:"binDir" yaml:"binDir"`
|
||||
// Path to your project taps
|
||||
TapDir string `json:"tapDir" yaml:"tapDir"`
|
||||
// Path for the downloaded sources
|
||||
TempDir string `json:"tempDir" yaml:"tempDir"`
|
||||
// Path to the versioned executables
|
||||
CellarDir string `json:"cellarDir" yaml:"cellarDir"`
|
||||
// List of packages that should be installed
|
||||
Packages []Package `json:"packages,omitempty" yaml:"packages,omitempty"`
|
||||
}
|
||||
46
pkg/config/package.go
Normal file
46
pkg/config/package.go
Normal file
@ -0,0 +1,46 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
// Name of the tap
|
||||
Tap string `json:"tap" yaml:"tap"`
|
||||
// Name of the package
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
// Names of the packages
|
||||
Names []string `json:"names,omitempty" yaml:"names,omitempty"`
|
||||
// Additional command args
|
||||
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
||||
// Version of the package to install
|
||||
Version string `json:"version" yaml:"version"`
|
||||
}
|
||||
|
||||
func (c Package) AllNames() []string {
|
||||
names := c.Names
|
||||
if len(names) == 0 {
|
||||
names = append(names, c.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (c Package) String() string {
|
||||
return fmt.Sprintf("Names: %s, Version: %s Tap: %s", c.AllNames(), c.Version, c.Tap)
|
||||
}
|
||||
|
||||
func (c Package) URL() (string, error) {
|
||||
// foomo/tap/aws/kubectl
|
||||
parts := strings.Split(c.Tap, "/")
|
||||
if len(parts) < 4 {
|
||||
return "", fmt.Errorf("invalid tap format: %s", c.Tap)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"https://raw.githubusercontent.com/%s/ownbrew-%s/main/%s/%s.sh",
|
||||
parts[0],
|
||||
parts[1],
|
||||
parts[2],
|
||||
parts[3],
|
||||
), nil
|
||||
}
|
||||
3
pkg/config/version.go
Normal file
3
pkg/config/version.go
Normal file
@ -0,0 +1,3 @@
|
||||
package config
|
||||
|
||||
const Version = "1.0"
|
||||
342
pkg/ownbrew/ownbrew.go
Normal file
342
pkg/ownbrew/ownbrew.go
Normal file
@ -0,0 +1,342 @@
|
||||
package ownbrew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/ownbrew/pkg/config"
|
||||
"github.com/foomo/ownbrew/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
Ownbrew struct {
|
||||
l *slog.Logger
|
||||
dry bool
|
||||
binDir string
|
||||
tapDir string
|
||||
tempDir string
|
||||
cellarDir string
|
||||
packages []config.Package
|
||||
timeout time.Duration
|
||||
}
|
||||
Option func(*Ownbrew) error
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func WithDry(v bool) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.dry = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPackages(v ...config.Package) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.packages = append(o.packages, v...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithBinDir(v string) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.binDir = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTempDir(v string) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.tempDir = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTapDir(v string) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.tapDir = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCellarDir(v string) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.cellarDir = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(v time.Duration) Option {
|
||||
return func(o *Ownbrew) error {
|
||||
o.timeout = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func New(l *slog.Logger, opts ...Option) (*Ownbrew, error) {
|
||||
inst := &Ownbrew{
|
||||
l: l,
|
||||
binDir: "bin",
|
||||
tempDir: ".ownbrew/tmp",
|
||||
tapDir: ".ownbrew/tap",
|
||||
cellarDir: ".ownbrew/bin",
|
||||
timeout: 3 * time.Minute,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
if err := opt(inst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, dir := range []string{inst.binDir, inst.tempDir, inst.tapDir, inst.cellarDir} {
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (o *Ownbrew) Install(ctx context.Context) error {
|
||||
o.l.Debug("install:", "os", runtime.GOOS, "arch", runtime.GOARCH)
|
||||
|
||||
for _, pkg := range o.packages {
|
||||
var install bool
|
||||
cellarFilenames, err := o.cellarFilenames(pkg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve cellar filename for package")
|
||||
}
|
||||
for _, cellarFilename := range cellarFilenames {
|
||||
if cellarExists, err := o.cellarExists(cellarFilename); err != nil {
|
||||
return errors.Wrapf(err, "failed to check cellar: %s", cellarFilename)
|
||||
} else if !cellarExists {
|
||||
install = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if install {
|
||||
if pkg.Tap == "" {
|
||||
if err := o.installLocal(ctx, pkg); err != nil {
|
||||
return errors.Wrap(err, "failed to install local tap")
|
||||
}
|
||||
} else {
|
||||
if err := o.installRemote(ctx, pkg); err != nil {
|
||||
return errors.Wrap(err, "failed to install remote tap")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
o.l.Debug("exists:", "pkg", pkg.String())
|
||||
}
|
||||
|
||||
// create symlink
|
||||
if !o.dry {
|
||||
for _, name := range pkg.AllNames() {
|
||||
filename := filepath.Join(o.binDir, name)
|
||||
cellarFilename, err := o.cellarFilename(name, pkg.Version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve cellar filename")
|
||||
}
|
||||
o.l.Debug("creating symlink:", "source", cellarFilename, "target", filename)
|
||||
if err := o.symlink(cellarFilename, filename); err != nil {
|
||||
return errors.Wrapf(err, "failed to symlink: %s => %s", cellarFilename, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (o *Ownbrew) symlink(source, target string) error {
|
||||
// remove existing
|
||||
if err := os.Remove(target); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
prefix, err := filepath.Rel(filepath.Base(target), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix = strings.TrimSuffix(prefix, ".")
|
||||
|
||||
o.l.Debug("symlink:", prefix+source, target)
|
||||
return os.Symlink(prefix+source, target)
|
||||
}
|
||||
|
||||
func (o *Ownbrew) cellarExists(filename string) (bool, error) {
|
||||
if stat, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to stat cellar (%s)", filename)
|
||||
} else if stat.IsDir() {
|
||||
return true, fmt.Errorf("not a file (%s)", filename)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (o *Ownbrew) cellarFilenames(pkg config.Package) ([]string, error) {
|
||||
names := pkg.AllNames()
|
||||
ret := make([]string, len(names))
|
||||
for i, name := range names {
|
||||
filename, err := o.cellarFilename(name, pkg.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = filename
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *Ownbrew) cellarFilename(name, version string) (string, error) {
|
||||
ret := filepath.Join(
|
||||
o.cellarDir,
|
||||
fmt.Sprintf("%s-%s-%s-%s", name, version, runtime.GOOS, runtime.GOARCH),
|
||||
)
|
||||
|
||||
info, err := os.Stat(ret)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return "", errors.Wrap(err, "failed to retrieve file info")
|
||||
}
|
||||
if info.IsDir() {
|
||||
ret = path.Join(ret, name)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *Ownbrew) installLocal(ctx context.Context, pkg config.Package) error {
|
||||
filename := filepath.Join(o.tapDir, pkg.Name+".sh")
|
||||
o.l.Info("installing local:", "pkg", pkg.String(), "filename", filename)
|
||||
|
||||
if exists, err := o.localTapExists(filename); err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
return fmt.Errorf("missing local tap: %s", filename)
|
||||
}
|
||||
|
||||
if o.dry {
|
||||
value, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read file")
|
||||
}
|
||||
util.Code(o.l, filename, string(value), "sh")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, filename,
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
pkg.Version,
|
||||
)
|
||||
cmd.Env = append(
|
||||
os.Environ(),
|
||||
"BIN_DIR="+o.cellarDir,
|
||||
"TAP_DIR="+o.tapDir,
|
||||
"TEMP_DIR="+o.tempDir,
|
||||
)
|
||||
cmd.Args = append(cmd.Args, pkg.Args...)
|
||||
o.l.Debug("running:", "cmd", cmd.String())
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return errors.Wrap(err, string(out))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Ownbrew) installRemote(ctx context.Context, pkg config.Package) error {
|
||||
url, err := pkg.URL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.l.Info("installing remote:", "pkg", pkg.String(), "url", url)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, o.timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve script")
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve script")
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("failed to retrieve script: %s", resp.Status)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
script, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.dry {
|
||||
util.Code(o.l, url, string(script), "sh")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "bash", "-s",
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
pkg.Version,
|
||||
)
|
||||
cmd.Env = append(
|
||||
os.Environ(),
|
||||
"BIN_DIR="+o.cellarDir,
|
||||
"TAP_DIR="+o.tapDir,
|
||||
"TEMP_DIR="+o.tempDir,
|
||||
)
|
||||
cmd.Args = append(cmd.Args, pkg.Args...)
|
||||
cmd.Stdin = bytes.NewReader(script)
|
||||
cmd.Stdout = os.Stdout
|
||||
if o.l.Enabled(ctx, slog.LevelDebug) {
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
if err := cmd.Run(); err != nil {
|
||||
util.Code(o.l, url, string(script), "sh")
|
||||
return errors.Wrap(err, "failed to install")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Ownbrew) localTapExists(filename string) (bool, error) {
|
||||
if stat, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to stat tap (%s)", filename)
|
||||
} else if stat.IsDir() {
|
||||
return true, fmt.Errorf("not an executeable: %s", filename)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
19
pkg/util/code.go
Normal file
19
pkg/util/code.go
Normal file
@ -0,0 +1,19 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/quick"
|
||||
)
|
||||
|
||||
func Code(l *slog.Logger, title, code, lexer string) {
|
||||
border := strings.Repeat("-", 80)
|
||||
l.Info(fmt.Sprintf("\n%s\n%s\n%s", border, title, border))
|
||||
if err := quick.Highlight(os.Stdout, code, lexer, "terminal", "monokai"); err != nil {
|
||||
l.Debug(err.Error())
|
||||
fmt.Println(code)
|
||||
}
|
||||
}
|
||||
98
pkg/util/highlight.go
Normal file
98
pkg/util/highlight.go
Normal file
@ -0,0 +1,98 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
func Highlight(source string) string {
|
||||
out := &numberWriter{
|
||||
w: bytes.NewBufferString(""),
|
||||
currentLine: 1,
|
||||
}
|
||||
// Determine lexer.
|
||||
l := lexers.Get("yaml")
|
||||
if l == nil {
|
||||
l = lexers.Analyse(source)
|
||||
}
|
||||
if l == nil {
|
||||
l = lexers.Fallback
|
||||
}
|
||||
l = chroma.Coalesce(l)
|
||||
|
||||
// Determine formatter.
|
||||
f := formatters.Get("terminal256")
|
||||
if f == nil {
|
||||
f = formatters.Fallback
|
||||
}
|
||||
|
||||
// Determine style.
|
||||
s := styles.Get("monokai")
|
||||
if s == nil {
|
||||
s = styles.Fallback
|
||||
}
|
||||
|
||||
it, err := l.Tokenise(nil, source)
|
||||
if err != nil {
|
||||
pterm.Error.Println(err.Error())
|
||||
}
|
||||
|
||||
if err = f.Format(out, s, it); err != nil {
|
||||
pterm.Error.Println(err.Error())
|
||||
}
|
||||
|
||||
return out.w.String()
|
||||
}
|
||||
|
||||
type numberWriter struct {
|
||||
w *bytes.Buffer
|
||||
currentLine uint64
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (w *numberWriter) Write(p []byte) (int, error) {
|
||||
// Early return.
|
||||
// Can't calculate the line numbers until the line breaks are made, so store them all in a buffer.
|
||||
if !bytes.Contains(p, []byte{'\n'}) {
|
||||
w.buf = append(w.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var (
|
||||
original = p
|
||||
tokenLen uint
|
||||
)
|
||||
for i, c := range original {
|
||||
tokenLen++
|
||||
if c != '\n' {
|
||||
continue
|
||||
}
|
||||
|
||||
token := p[:tokenLen]
|
||||
p = original[i+1:]
|
||||
tokenLen = 0
|
||||
|
||||
format := "%4d |\t%s%s"
|
||||
if w.currentLine > 9999 {
|
||||
format = "%d |\t%s%s"
|
||||
}
|
||||
format = "\033[34m" + format + "\033[0m"
|
||||
|
||||
if _, err := fmt.Fprintf(w.w, format, w.currentLine, string(w.buf), string(token)); err != nil {
|
||||
return i + 1, err
|
||||
}
|
||||
w.buf = w.buf[:0]
|
||||
w.currentLine++
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
w.buf = append(w.buf, p...)
|
||||
}
|
||||
return len(original), nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user