├── .github └── workflows │ └── go.yml ├── .gitignore ├── .goreleaser.yaml ├── .idea ├── .gitignore ├── modules.xml └── pocketbase-type-generator.iml ├── LICENSE.md ├── README.md ├── cmd └── pocketbase-ts-generator │ └── main.go ├── example ├── pocketbase_command.go └── pocketbase_hook.go ├── go.mod ├── go.sum ├── internal ├── cmd │ └── flagparser.go ├── core │ └── core.go ├── credentials │ └── credentials.go ├── forms │ ├── collections.go │ ├── credentials.go │ └── output.go ├── generator │ └── property.go ├── interpreter │ └── property.go ├── pocketbase_api │ ├── api.go │ └── collections.go └── pocketbase_core │ ├── collections.go │ └── converter.go └── pkg └── pocketbase-ts-generator ├── cmd.go ├── core.go └── hook.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: basebuild 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | env: 11 | flags: "" 12 | steps: 13 | - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 14 | run: echo "flags=--snapshot" >> $GITHUB_ENV 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version: '>=1.23.4' 25 | 26 | - name: Run GoReleaser 27 | uses: goreleaser/goreleaser-action@v6 28 | with: 29 | distribution: goreleaser 30 | version: '~> v2' 31 | args: release --clean ${{ env.flags }} 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | credentials.env 28 | credentials.enc.env 29 | 30 | /.builds/ 31 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: pocketbase-ts-generator 4 | 5 | dist: .builds 6 | 7 | before: 8 | hooks: 9 | - go mod tidy 10 | 11 | builds: 12 | - id: build_noncgo 13 | main: ./cmd/pocketbase-ts-generator 14 | binary: pocketbase-ts-generator 15 | ldflags: 16 | - -s -w -X github.com/Vogeslu/pocketbase-ts-generator.Version={{ .Version }} 17 | env: 18 | - CGO_ENABLED=0 19 | goos: 20 | - linux 21 | - windows 22 | - darwin 23 | goarch: 24 | - amd64 25 | - arm64 26 | - arm 27 | - s390x 28 | - ppc64le 29 | goarm: 30 | - 7 31 | ignore: 32 | - goos: windows 33 | goarch: arm 34 | - goos: windows 35 | goarch: s390x 36 | - goos: windows 37 | goarch: ppc64le 38 | - goos: darwin 39 | goarch: arm 40 | - goos: darwin 41 | goarch: s390x 42 | - goos: darwin 43 | goarch: ppc64le 44 | 45 | release: 46 | draft: true 47 | 48 | archives: 49 | - id: archive_noncgo 50 | builds: [build_noncgo] 51 | format: zip 52 | files: 53 | - LICENSE.md 54 | 55 | checksum: 56 | name_template: 'checksums.txt' 57 | 58 | snapshot: 59 | version_template: '{{ incpatch .Version }}-next' 60 | 61 | changelog: 62 | sort: asc 63 | filters: 64 | exclude: 65 | - '^example:' -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/pocketbase-type-generator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 - present, Luca Voges 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pocketbase-ts-generator 2 | 3 | Application / Library to generate typescript interfaces from pocketbase collections 4 | 5 | ## Installation & Usage 6 | 7 | ### Standalone executable 8 | 9 | Pocketbase-ts-generator can be run as a standalone application accessing an existing pocketbase-server with credentials. 10 | 11 | 1. Download the latest version from [Releases](https://github.com/Vogeslu/pocketbase-ts-generator/releases). 12 | 2. Extract the archive and run the pocketbase-ts-generator executable 13 | 14 | By default the generator will prompt you for your pocketbase credentials, the collections to export and the output path. 15 | 16 | ```bash 17 | $ pocketbse-ts-generator 18 | 19 | --- 20 | 21 | 22 | Hostname 23 | > 24 | 25 | Email address 26 | > 27 | 28 | Password 29 | > 30 | 31 | enter next 32 | 33 | --- 34 | 35 | Select collections to generate types from 36 | > ✓ users (10 fields) 37 | ✓ everything (17 fields) 38 | • _mfas (System, 6 fields) 39 | • _otps (System, 7 fields) 40 | • _externalAuths (System, 7 fields) 41 | • _authOrigins (System, 6 fields) 42 | • _superusers (System, 8 fields) 43 | 44 | x toggle • ↑ up • ↓ down • / filter • enter submit • ctrl+a select all 45 | ``` 46 | 47 | After submitting the credentials, you can save them in a credentials.env file. You have the choice to save them plain or encrypted with a custom passphrase. So when you run the pocketbase-ts-generator again, you can skip the credentials and just enter the encryption passphrase if you chose an encrypted credentials file. 48 | 49 | If you don't want to use the built-in prompts, you can use flags to enter the required information: 50 | 51 | ``` 52 | -a, --collections-all Select all collections include system collections 53 | -x, --collections-exclude strings Collections to exclude 54 | -i, --collections-include strings Collections to include (Overrides default selection or all collections) 55 | -d, --disable-form Disable form 56 | -l, --disable-logs Disable logs, only return result if no output is specified or errors 57 | -e, --email string Pocketbase email 58 | -c, --encryption-password string credentials.enc.env password 59 | -h, --help help for generate-ts 60 | -u, --host-url string Pocketbase host url (e. g. http://127.0.0.1:8090) 61 | --non-required-optional Make non required fields optional properties (with question mark) 62 | -o, --output string Output file path 63 | -p, --password string Pocketbase password 64 | ``` 65 | 66 | To export all collections that are not marked as system collections (e.g., _superusers), you can type the following command 67 | 68 | ```bash 69 | $ pocketbase-ts-generator -d -u 127.0.0.1:8090 -e [SUPERUSER_EMAIL] -p [SUPERUSER_PASSWORD] -o [OUTPUT_FILE_PATH] 70 | ``` 71 | 72 | Executing this command will cause the generator to connect to the specified PocketBase server, retrieve all collections and save the typescript definitions to the specified file. 73 | 74 | Alternatively, you can print the definitions directly to the console with the `-l` flag and without the `-o`. 75 | 76 | ```bash 77 | $ pocketbase-ts-generator -d -u 127.0.0.1:8090 -e [SUPERUSER_EMAIL] -p [SUPERUSER_PASSWORD] -l 78 | ``` 79 | 80 | ### Implement in Go 81 | 82 | You can use the pocketbase-ts-generator implemented in your pocketbase project either as a command or as a hook. With a hook you can automatically generate a new typescript file whenever a collection is updated, created or deleted. 83 | 84 | You can add the library to your `go.mod` using this command: 85 | 86 | ```bash 87 | $ go get -u github.com/Vogeslu/pocketbase-ts-generator 88 | ``` 89 | 90 | Examples for both cases are available in the `./example` directory. 91 | 92 | #### Implement as command 93 | 94 | If you register the command you can use it just like the standalone executable without entering the credentials. 95 | 96 | ```go 97 | package main 98 | 99 | import ( 100 | "github.com/Vogeslu/pocketbase-ts-generator/pkg/pocketbase-ts-generator" 101 | "github.com/pocketbase/pocketbase" 102 | "github.com/rs/zerolog/log" 103 | ) 104 | 105 | func main() { 106 | app := pocketbase.New() 107 | 108 | pocketbase_ts_generator.RegisterCommand(app) 109 | 110 | if err := app.Start(); err != nil { 111 | log.Fatal().Err(err) 112 | } 113 | } 114 | ``` 115 | 116 | You can run the generate command by typing: 117 | 118 | ```bash 119 | $ go run ./path/to/main.go generate-ts 120 | ``` 121 | 122 | The following options are available: 123 | 124 | ``` 125 | -a, --collections-all Select all collections include system collections 126 | -x, --collections-exclude strings Collections to exclude 127 | -i, --collections-include strings Collections to include (Overrides default selection or all collections) 128 | -h, --help help for generate-ts 129 | --non-required-optional Make non required fields optional properties (with question mark) 130 | -o, --output string Output file path 131 | ``` 132 | 133 | #### Implement as a hook 134 | 135 | If you want to automatically generate new typescript definitions whenever a collection is updated, created, or deleted, you can use the following example: 136 | 137 | ```go 138 | package main 139 | 140 | import ( 141 | "github.com/Vogeslu/pocketbase-ts-generator/pkg/pocketbase-ts-generator" 142 | "github.com/pocketbase/pocketbase" 143 | "github.com/rs/zerolog/log" 144 | ) 145 | 146 | func main() { 147 | app := pocketbase.New() 148 | 149 | pocketbase_ts_generator.RegisterHook(app, &pocketbase_ts_generator.GeneratorOptions{ 150 | Output: "test.ts", 151 | }) 152 | 153 | if err := app.Start(); err != nil { 154 | log.Fatal().Err(err) 155 | } 156 | } 157 | ``` 158 | 159 | When running the pocketbase-server with `go run ./path/to/main.go serve` and performing a collection change, the typescript definitions are saved in `test.ts`. 160 | 161 | -------------------------------------------------------------------------------- /cmd/pocketbase-ts-generator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/core" 6 | "github.com/Vogeslu/pocketbase-ts-generator/internal/credentials" 7 | "github.com/Vogeslu/pocketbase-ts-generator/internal/forms" 8 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 9 | "github.com/rs/zerolog" 10 | "github.com/rs/zerolog/log" 11 | "github.com/spf13/cobra" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 17 | zerolog.TimeFieldFormat = zerolog.TimeFormatUnix 18 | 19 | rootCmd := cmd.GetGenerateTsCommand(false, func(cmd *cobra.Command, args []string, generatorFlags *cmd.GeneratorFlags) { 20 | if generatorFlags.DisableLogs { 21 | zerolog.SetGlobalLevel(4) 22 | } else { 23 | zerolog.SetGlobalLevel(1) 24 | } 25 | 26 | pbCredentials := &credentials.Credentials{ 27 | Host: generatorFlags.Host, 28 | Email: generatorFlags.Email, 29 | Password: generatorFlags.Password, 30 | } 31 | 32 | if !generatorFlags.DisableForm { 33 | storeCredentials := forms.AskCredentials(pbCredentials) 34 | 35 | if storeCredentials { 36 | forms.AskStoreCredentials(pbCredentials) 37 | } 38 | } else { 39 | credentialExist, isEncrypted, err := credentials.CheckExistingCredentials() 40 | if err != nil { 41 | log.Fatal().Err(err).Msg("Could not check for credentials") 42 | } 43 | 44 | if credentialExist { 45 | if isEncrypted { 46 | err = pbCredentials.Decrypt(generatorFlags.EncryptionPassword) 47 | if err != nil { 48 | log.Fatal().Err(err).Msg("Could not decrypt stored credentials") 49 | } 50 | } else { 51 | err = pbCredentials.Load() 52 | if err != nil { 53 | log.Fatal().Err(err).Msg("Could not load stored credentials") 54 | } 55 | } 56 | } 57 | } 58 | 59 | pocketBase := pocketbase_api.New(pbCredentials) 60 | 61 | err := pocketBase.Authenticate() 62 | if err != nil { 63 | log.Fatal().Err(err).Msg("Authentication error") 64 | } 65 | 66 | collections, err := pocketBase.GetCollections() 67 | if err != nil { 68 | log.Fatal().Err(err).Msg("Could not retrieve collections") 69 | } 70 | 71 | var selectedCollections []*pocketbase_api.Collection 72 | 73 | if !generatorFlags.DisableForm { 74 | selectedCollections = forms.AskCollectionSelection(collections.Items) 75 | generatorFlags.Output = forms.AskOutputTarget(generatorFlags.Output) 76 | } else { 77 | selectedCollections = forms.GetSelectedCollections(generatorFlags, collections.Items) 78 | } 79 | 80 | core.ProcessCollections(selectedCollections, collections.Items, generatorFlags) 81 | }) 82 | 83 | err := rootCmd.Execute() 84 | if err != nil { 85 | log.Fatal().Err(err).Msg("Failed processing command") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /example/pocketbase_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/pkg/pocketbase-ts-generator" 5 | "github.com/pocketbase/pocketbase" 6 | "github.com/rs/zerolog/log" 7 | ) 8 | 9 | func main() { 10 | app := pocketbase.New() 11 | 12 | pocketbase_ts_generator.RegisterCommand(app) 13 | 14 | if err := app.Start(); err != nil { 15 | log.Fatal().Err(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/pocketbase_hook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/pkg/pocketbase-ts-generator" 5 | "github.com/pocketbase/pocketbase" 6 | "github.com/rs/zerolog/log" 7 | ) 8 | 9 | func main() { 10 | app := pocketbase.New() 11 | 12 | pocketbase_ts_generator.RegisterHook(app, &pocketbase_ts_generator.GeneratorOptions{ 13 | Output: "test.ts", 14 | }) 15 | 16 | if err := app.Start(); err != nil { 17 | log.Fatal().Err(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Vogeslu/pocketbase-ts-generator 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/charmbracelet/huh v0.6.0 7 | github.com/iancoleman/strcase v0.3.0 8 | github.com/pocketbase/pocketbase v0.23.12 9 | github.com/rs/zerolog v1.33.0 10 | github.com/spf13/cobra v1.8.1 11 | golang.org/x/crypto v0.31.0 12 | ) 13 | 14 | require ( 15 | github.com/AlecAivazis/survey/v2 v2.3.7 // indirect 16 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 17 | github.com/atotto/clipboard v0.1.4 // indirect 18 | github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect 19 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect 20 | github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect 21 | github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect 22 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect 23 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44 // indirect 24 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect 25 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect 26 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect 27 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect 28 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect 29 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect 30 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect 31 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect 32 | github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 // indirect 33 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect 34 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect 35 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect 36 | github.com/aws/smithy-go v1.22.1 // indirect 37 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 38 | github.com/catppuccin/go v0.2.0 // indirect 39 | github.com/charmbracelet/bubbles v0.20.0 // indirect 40 | github.com/charmbracelet/bubbletea v1.1.0 // indirect 41 | github.com/charmbracelet/lipgloss v0.13.0 // indirect 42 | github.com/charmbracelet/x/ansi v0.2.3 // indirect 43 | github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect 44 | github.com/charmbracelet/x/term v0.2.0 // indirect 45 | github.com/disintegration/imaging v1.6.2 // indirect 46 | github.com/domodwyer/mailyak/v3 v3.6.2 // indirect 47 | github.com/dustin/go-humanize v1.0.1 // indirect 48 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 49 | github.com/fatih/color v1.18.0 // indirect 50 | github.com/gabriel-vasile/mimetype v1.4.7 // indirect 51 | github.com/ganigeorgiev/fexpr v0.4.1 // indirect 52 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect 53 | github.com/golang-jwt/jwt/v4 v4.5.1 // indirect 54 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 55 | github.com/google/uuid v1.6.0 // indirect 56 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 57 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 58 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 59 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 60 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 61 | github.com/mattn/go-colorable v0.1.13 // indirect 62 | github.com/mattn/go-isatty v0.0.20 // indirect 63 | github.com/mattn/go-localereader v0.0.1 // indirect 64 | github.com/mattn/go-runewidth v0.0.16 // indirect 65 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 66 | github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect 67 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 68 | github.com/muesli/cancelreader v0.2.2 // indirect 69 | github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect 70 | github.com/ncruces/go-strftime v0.1.9 // indirect 71 | github.com/pocketbase/dbx v1.11.0 // indirect 72 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 73 | github.com/rivo/uniseg v0.4.7 // indirect 74 | github.com/spf13/cast v1.7.1 // indirect 75 | github.com/spf13/pflag v1.0.5 // indirect 76 | go.opencensus.io v0.24.0 // indirect 77 | gocloud.dev v0.40.0 // indirect 78 | golang.org/x/image v0.23.0 // indirect 79 | golang.org/x/net v0.33.0 // indirect 80 | golang.org/x/oauth2 v0.24.0 // indirect 81 | golang.org/x/sync v0.10.0 // indirect 82 | golang.org/x/sys v0.28.0 // indirect 83 | golang.org/x/term v0.27.0 // indirect 84 | golang.org/x/text v0.21.0 // indirect 85 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect 86 | google.golang.org/api v0.214.0 // indirect 87 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect 88 | google.golang.org/grpc v1.69.2 // indirect 89 | google.golang.org/protobuf v1.36.0 // indirect 90 | modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 // indirect 91 | modernc.org/libc v1.55.3 // indirect 92 | modernc.org/mathutil v1.6.0 // indirect 93 | modernc.org/memory v1.8.0 // indirect 94 | modernc.org/sqlite v1.34.4 // indirect 95 | modernc.org/strutil v1.2.0 // indirect 96 | modernc.org/token v1.1.0 // indirect 97 | ) 98 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= 3 | cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= 4 | cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= 5 | cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= 6 | cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= 7 | cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= 8 | cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= 9 | cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 10 | cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= 11 | cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= 12 | cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= 13 | cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= 14 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 15 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 16 | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= 17 | github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= 18 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 19 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 20 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 21 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 22 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 23 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 24 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 25 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 26 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 27 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 28 | github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= 29 | github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= 30 | github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= 31 | github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= 32 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= 33 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= 34 | github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= 35 | github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M= 36 | github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ= 37 | github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs= 38 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= 39 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= 40 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44 h1:2zxMLXLedpB4K1ilbJFxtMKsVKaexOqDttOhc0QGm3Q= 41 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.44/go.mod h1:VuLHdqwjSvgftNC7yqPWyGVhEwPmJpeRi07gOgOfHF8= 42 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= 43 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= 44 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= 45 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= 46 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= 47 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= 48 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= 49 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo= 50 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= 51 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= 52 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY= 53 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ= 54 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= 55 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= 56 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w= 57 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c= 58 | github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE= 59 | github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q= 60 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= 61 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= 62 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= 63 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= 64 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY= 65 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= 66 | github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= 67 | github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 68 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 69 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 70 | github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= 71 | github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= 72 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 73 | github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= 74 | github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= 75 | github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c= 76 | github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= 77 | github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= 78 | github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= 79 | github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= 80 | github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= 81 | github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY= 82 | github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= 83 | github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= 84 | github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= 85 | github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= 86 | github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= 87 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 88 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 89 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 90 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 91 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 92 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 93 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 94 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 95 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 96 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 97 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 98 | github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= 99 | github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= 100 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 101 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 102 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 103 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 104 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 105 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 106 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 107 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 108 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 109 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 110 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 111 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 112 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 113 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 114 | github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= 115 | github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= 116 | github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k= 117 | github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= 118 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 119 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 120 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 121 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 122 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= 123 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= 124 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 125 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 126 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 127 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 128 | github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= 129 | github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 130 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 131 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 132 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 133 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 134 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 135 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 136 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 137 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 138 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 139 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 140 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 141 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 142 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 143 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 144 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 145 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 146 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 147 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 148 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 149 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 150 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 151 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 152 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 153 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 154 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 155 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= 156 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 157 | github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= 158 | github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 159 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 160 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 161 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 162 | github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= 163 | github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= 164 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= 165 | github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= 166 | github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= 167 | github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= 168 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 169 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 170 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 171 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 172 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 173 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 174 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 175 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 176 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 177 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 178 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 179 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 180 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 181 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 182 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 183 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 184 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 185 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 186 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 187 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 188 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 189 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 190 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 191 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 192 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 193 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 194 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 195 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 196 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 197 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 198 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 199 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= 200 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 201 | github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= 202 | github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 203 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 204 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 205 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 206 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 207 | github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= 208 | github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= 209 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= 210 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 211 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 212 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 213 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 214 | github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU= 215 | github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= 216 | github.com/pocketbase/pocketbase v0.23.12 h1:HB4THFbzaliF0C3wvpx+kNOZxIwCEMDqN3/17gn5N7E= 217 | github.com/pocketbase/pocketbase v0.23.12/go.mod h1:OcFJNMO0Vzt3f9+lweMbup6iL7V13ckxu1pdEY6FeM0= 218 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 219 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 220 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 221 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 222 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 223 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 224 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 225 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 226 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 227 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 228 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 229 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 230 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 231 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 232 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 233 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 234 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 235 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 236 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 237 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 238 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 239 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 240 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 241 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 242 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 243 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 244 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 245 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 246 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 247 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 248 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 249 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= 250 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= 251 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= 252 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= 253 | go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= 254 | go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 255 | go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= 256 | go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= 257 | go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= 258 | go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= 259 | go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= 260 | go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= 261 | go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= 262 | go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= 263 | gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng= 264 | gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= 265 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 266 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 267 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 268 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 269 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 270 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 271 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 272 | golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= 273 | golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= 274 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 275 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 276 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 277 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 278 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 279 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 280 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 281 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 282 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 283 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 284 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 285 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 286 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 287 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 288 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 289 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 290 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 291 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 292 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 293 | golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= 294 | golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 295 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 297 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 299 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 300 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 301 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 302 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 303 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 304 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 308 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 309 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 310 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 311 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 312 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 313 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 314 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 315 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 316 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 317 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 318 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 319 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 320 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 321 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 322 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 323 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 324 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 325 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 326 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 327 | golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 328 | golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 329 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 330 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 331 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 332 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 333 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 334 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 335 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 336 | golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= 337 | golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 338 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 339 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 340 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= 341 | golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 342 | google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= 343 | google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= 344 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 345 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 346 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 347 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 348 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 349 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 350 | google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= 351 | google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= 352 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= 353 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 354 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= 355 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= 356 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 357 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 358 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 359 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 360 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 361 | google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= 362 | google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 363 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 364 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 365 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 366 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 367 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 368 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 369 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 370 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 371 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 372 | google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= 373 | google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 374 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 375 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 376 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 377 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 378 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 379 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 380 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 381 | modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= 382 | modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= 383 | modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= 384 | modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= 385 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= 386 | modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= 387 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= 388 | modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= 389 | modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 h1:ovz6yUKX71igz2yvk4NpiCL5fvdjZAI+DhuDEGx1xyU= 390 | modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= 391 | modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= 392 | modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= 393 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= 394 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= 395 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= 396 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= 397 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 398 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 399 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= 400 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= 401 | modernc.org/sqlite v1.34.4 h1:sjdARozcL5KJBvYQvLlZEmctRgW9xqIZc2ncN7PU0P8= 402 | modernc.org/sqlite v1.34.4/go.mod h1:3QQFCG2SEMtc2nv+Wq4cQCH7Hjcg+p/RMlS1XK+zwbk= 403 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= 404 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 405 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 406 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 407 | -------------------------------------------------------------------------------- /internal/cmd/flagparser.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | type GeneratorFlags struct { 8 | DisableForm bool 9 | DisableLogs bool 10 | 11 | Host string 12 | Email string 13 | Password string 14 | 15 | EncryptionPassword string 16 | 17 | AllCollections bool 18 | CollectionsInclude []string 19 | CollectionsExclude []string 20 | 21 | Output string 22 | 23 | // Extra flags 24 | MakeNonRequiredOptional bool 25 | } 26 | 27 | func GetGenerateTsCommand(fromPocketBase bool, callback func(cmd *cobra.Command, args []string, generatorFlags *GeneratorFlags)) *cobra.Command { 28 | generatorFlags := &GeneratorFlags{} 29 | 30 | rootCmd := &cobra.Command{ 31 | Use: "generate-ts", 32 | Short: "Generate typescript interfaces from pocketbase_api", 33 | Long: "Generate typescript interfaces based on pocketbase_api collection definitions", 34 | Run: func(cmd *cobra.Command, args []string) { 35 | callback(cmd, args, generatorFlags) 36 | }, 37 | } 38 | 39 | if !fromPocketBase { 40 | rootCmd.PersistentFlags().BoolVarP(&generatorFlags.DisableForm, "disable-form", "d", false, "Disable form") 41 | rootCmd.PersistentFlags().BoolVarP(&generatorFlags.DisableLogs, "disable-logs", "l", false, "Disable logs, only return result if no output is specified or errors") 42 | 43 | rootCmd.PersistentFlags().StringVarP(&generatorFlags.Host, "host-url", "u", "", "Pocketbase host url (e. g. http://127.0.0.1:8090)") 44 | rootCmd.PersistentFlags().StringVarP(&generatorFlags.Host, "email", "e", "", "Pocketbase email") 45 | rootCmd.PersistentFlags().StringVarP(&generatorFlags.Host, "password", "p", "", "Pocketbase password") 46 | 47 | rootCmd.PersistentFlags().StringVarP(&generatorFlags.EncryptionPassword, "encryption-password", "c", "", "credentials.enc.env password") 48 | } 49 | 50 | rootCmd.PersistentFlags().BoolVarP(&generatorFlags.DisableForm, "collections-all", "a", false, "Select all collections include system collections") 51 | rootCmd.PersistentFlags().StringSliceVarP(&generatorFlags.CollectionsInclude, "collections-include", "i", []string{}, "Collections to include (Overrides default selection or all collections)") 52 | rootCmd.PersistentFlags().StringSliceVarP(&generatorFlags.CollectionsExclude, "collections-exclude", "x", []string{}, "Collections to exclude") 53 | 54 | rootCmd.PersistentFlags().StringVarP(&generatorFlags.Output, "output", "o", "", "Output file path") 55 | 56 | rootCmd.PersistentFlags().BoolVar(&generatorFlags.MakeNonRequiredOptional, "non-required-optional", false, "Make non required fields optional properties (with question mark)") 57 | 58 | return rootCmd 59 | } 60 | -------------------------------------------------------------------------------- /internal/core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 6 | "github.com/Vogeslu/pocketbase-ts-generator/internal/interpreter" 7 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 8 | "github.com/rs/zerolog/log" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func ProcessCollections(selectedCollections []*pocketbase_api.Collection, allCollections []pocketbase_api.Collection, generatorFlags *cmd.GeneratorFlags) { 14 | interpretedCollections := interpreter.InterpretCollections(selectedCollections, allCollections) 15 | 16 | output := make([]string, len(interpretedCollections)) 17 | 18 | for i, collection := range interpretedCollections { 19 | output[i] = collection.GetTypescriptInterface(generatorFlags) 20 | } 21 | 22 | joinedData := strings.Join(output, "\n\n") 23 | 24 | if generatorFlags.Output == "" { 25 | fmt.Println(joinedData) 26 | } else { 27 | err := os.WriteFile(generatorFlags.Output, []byte(joinedData), 0644) 28 | log.Info().Msgf("Saved generated interfaces to %s", generatorFlags.Output) 29 | if err != nil { 30 | log.Fatal().Err(err).Msg("Could not output contents") 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/credentials/credentials.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "bufio" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "errors" 10 | "fmt" 11 | "net/url" 12 | "os" 13 | "strings" 14 | 15 | "github.com/rs/zerolog/log" 16 | "golang.org/x/crypto/scrypt" 17 | ) 18 | 19 | var ( 20 | encryptedFileName string = "credentials.enc.env" 21 | rawFileName string = "credentials.env" 22 | ) 23 | 24 | type Credentials struct { 25 | Host string 26 | Email string 27 | Password string 28 | } 29 | 30 | func CheckExistingCredentials() (bool, bool, error) { 31 | _, err := os.Stat(encryptedFileName) 32 | 33 | if errors.Is(err, os.ErrNotExist) { 34 | _, err = os.Stat(rawFileName) 35 | 36 | if errors.Is(err, os.ErrNotExist) { 37 | return false, false, nil 38 | } else if err != nil { 39 | return false, false, err 40 | } 41 | 42 | return true, false, nil 43 | } else if err != nil { 44 | return false, false, err 45 | } 46 | 47 | return true, true, nil 48 | } 49 | 50 | func (credentials *Credentials) Encrypt(encryptionPassword string) error { 51 | log.Info().Msg("Encrypting data...") 52 | 53 | key, salt, err := deriveKey(encryptionPassword, nil) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | credentialsData := fmt.Sprintf("%s;%s;%s", 59 | url.QueryEscape(credentials.Host), 60 | url.QueryEscape(credentials.Email), 61 | url.QueryEscape(credentials.Password), 62 | ) 63 | 64 | encryptedCredentialsData, err := encryptString(credentialsData, key) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | _ = os.Remove(encryptedFileName) 70 | _ = os.Remove(rawFileName) 71 | 72 | data := []byte(fmt.Sprintf("SALT=%s\nDATA=%s", 73 | base64.URLEncoding.EncodeToString(salt), 74 | base64.URLEncoding.EncodeToString(encryptedCredentialsData), 75 | )) 76 | 77 | err = os.WriteFile(encryptedFileName, data, 0644) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (credentials *Credentials) Save() error { 86 | log.Info().Msg("Saving data...") 87 | 88 | _ = os.Remove(encryptedFileName) 89 | _ = os.Remove(rawFileName) 90 | 91 | data := []byte(fmt.Sprintf("HOST=%s\nEMAIL=%s\nPASSWORD=%s", 92 | credentials.Host, 93 | credentials.Email, 94 | credentials.Password, 95 | )) 96 | 97 | err := os.WriteFile(rawFileName, data, 0644) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func (credentials *Credentials) Decrypt(encryptionPassword string) error { 106 | log.Info().Msg("Decrypting data...") 107 | 108 | encryptedData := make(map[string][]byte) 109 | 110 | file, err := os.Open(encryptedFileName) 111 | if err != nil { 112 | return err 113 | } 114 | defer func(file *os.File) { 115 | err := file.Close() 116 | if err != nil { 117 | log.Warn().Err(err).Msg("Failed closing credentials file") 118 | } 119 | }(file) 120 | 121 | scanner := bufio.NewScanner(file) 122 | 123 | for scanner.Scan() { 124 | line := scanner.Text() 125 | 126 | if line == "" { 127 | continue 128 | } 129 | 130 | parts := strings.SplitN(line, "=", 2) 131 | 132 | if len(parts) < 2 { 133 | return errors.New("invalid credentials file content") 134 | } 135 | 136 | encryptedData[parts[0]], err = base64.URLEncoding.DecodeString(parts[1]) 137 | if err != nil { 138 | return err 139 | } 140 | } 141 | 142 | salt, ok := encryptedData["SALT"] 143 | if !ok { 144 | return errors.New("salt is missing") 145 | } 146 | 147 | encryptedCredentials, ok := encryptedData["DATA"] 148 | if !ok { 149 | return errors.New("data is missing") 150 | } 151 | 152 | key, salt, err := deriveKey(encryptionPassword, salt) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | decryptedCredentials, err := decryptBytes(encryptedCredentials, key) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | splitCredentials := strings.Split(decryptedCredentials, ";") 163 | 164 | credentials.Host, err = url.QueryUnescape(splitCredentials[0]) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | credentials.Email, err = url.QueryUnescape(splitCredentials[1]) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | credentials.Password, err = url.QueryUnescape(splitCredentials[2]) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func (credentials *Credentials) Load() error { 183 | log.Info().Msg("Loading data...") 184 | 185 | data := make(map[string]string) 186 | 187 | file, err := os.Open(rawFileName) 188 | if err != nil { 189 | return err 190 | } 191 | defer func(file *os.File) { 192 | err := file.Close() 193 | if err != nil { 194 | log.Warn().Err(err).Msg("Failed closing credentials file") 195 | } 196 | }(file) 197 | 198 | scanner := bufio.NewScanner(file) 199 | 200 | for scanner.Scan() { 201 | line := scanner.Text() 202 | 203 | if line == "" { 204 | continue 205 | } 206 | 207 | parts := strings.SplitN(line, "=", 2) 208 | 209 | if len(parts) < 2 { 210 | return errors.New("invalid credentials file") 211 | } 212 | 213 | data[parts[0]] = parts[1] 214 | } 215 | 216 | var ok bool 217 | 218 | credentials.Host, ok = data["HOST"] 219 | if !ok { 220 | return errors.New("host is missing") 221 | } 222 | 223 | credentials.Email, ok = data["EMAIL"] 224 | if !ok { 225 | return errors.New("email is missing") 226 | } 227 | 228 | credentials.Password, ok = data["PASSWORD"] 229 | if !ok { 230 | return errors.New("password is missing") 231 | } 232 | 233 | return nil 234 | } 235 | 236 | func encryptString(data string, key []byte) ([]byte, error) { 237 | blockCipher, err := aes.NewCipher(key) 238 | if err != nil { 239 | return nil, err 240 | } 241 | 242 | gcm, err := cipher.NewGCM(blockCipher) 243 | if err != nil { 244 | return nil, err 245 | } 246 | 247 | nonce := make([]byte, gcm.NonceSize()) 248 | if _, err = rand.Read(nonce); err != nil { 249 | return nil, err 250 | } 251 | 252 | ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil) 253 | 254 | return ciphertext, nil 255 | } 256 | 257 | func decryptBytes(data []byte, key []byte) (string, error) { 258 | blockCipher, err := aes.NewCipher(key) 259 | if err != nil { 260 | return "", err 261 | } 262 | 263 | gcm, err := cipher.NewGCM(blockCipher) 264 | if err != nil { 265 | return "", err 266 | } 267 | 268 | nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():] 269 | 270 | plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) 271 | if err != nil { 272 | return "", err 273 | } 274 | 275 | return string(plaintext), nil 276 | } 277 | 278 | func deriveKey(password string, salt []byte) ([]byte, []byte, error) { 279 | if salt == nil { 280 | salt = make([]byte, 32) 281 | if _, err := rand.Read(salt); err != nil { 282 | return nil, nil, err 283 | } 284 | } 285 | 286 | key, err := scrypt.Key([]byte(password), salt, 1048576, 8, 1, 32) 287 | if err != nil { 288 | return nil, nil, err 289 | } 290 | 291 | return key, salt, nil 292 | } 293 | -------------------------------------------------------------------------------- /internal/forms/collections.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 6 | "github.com/charmbracelet/huh" 7 | "github.com/rs/zerolog/log" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | func AskCollectionSelection(collections []pocketbase_api.Collection) []*pocketbase_api.Collection { 13 | options := make([]huh.Option[*pocketbase_api.Collection], len(collections)) 14 | 15 | for i, collection := range collections { 16 | options[i] = huh.NewOption(collection.String(), &collection).Selected(!collection.System) 17 | } 18 | 19 | sort.SliceStable(options, func(i, j int) bool { 20 | return options[i].Value.System != options[j].Value.System 21 | }) 22 | 23 | var output []*pocketbase_api.Collection 24 | 25 | form := huh.NewForm( 26 | huh.NewGroup( 27 | huh.NewMultiSelect[*pocketbase_api.Collection](). 28 | Options(options...). 29 | Title("Select collections to generate types from"). 30 | Value(&output), 31 | ), 32 | ) 33 | 34 | err := form.Run() 35 | if err != nil { 36 | log.Fatal().Err(err).Msg("Form error") 37 | } 38 | 39 | return output 40 | } 41 | 42 | func GetSelectedCollections(generatorFlags *cmd.GeneratorFlags, collections []pocketbase_api.Collection) []*pocketbase_api.Collection { 43 | var output []*pocketbase_api.Collection 44 | 45 | checkInclude := len(generatorFlags.CollectionsInclude) > 0 46 | 47 | for _, collection := range collections { 48 | if checkInclude && !containsIgnoreCase(generatorFlags.CollectionsInclude, collection.Name) { 49 | continue 50 | } else if containsIgnoreCase(generatorFlags.CollectionsExclude, collection.Name) { 51 | continue 52 | } else if !generatorFlags.AllCollections && collection.System && !(checkInclude && containsIgnoreCase(generatorFlags.CollectionsInclude, collection.Name)) { 53 | continue 54 | } 55 | 56 | output = append(output, &collection) 57 | } 58 | 59 | return output 60 | } 61 | 62 | func containsIgnoreCase(list []string, value string) bool { 63 | for _, v := range list { 64 | if strings.ToLower(v) == strings.ToLower(value) { 65 | return true 66 | } 67 | } 68 | 69 | return false 70 | } 71 | -------------------------------------------------------------------------------- /internal/forms/credentials.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | import ( 4 | "errors" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/credentials" 6 | "github.com/charmbracelet/huh" 7 | "github.com/rs/zerolog/log" 8 | ) 9 | 10 | func AskCredentials(pbCredentials *credentials.Credentials) bool { 11 | credentialExist, isEncrypted, err := credentials.CheckExistingCredentials() 12 | if err != nil { 13 | log.Fatal().Err(err).Msg("Could not check for credentials") 14 | } 15 | 16 | if credentialExist { 17 | if isEncrypted { 18 | var encryptionPassword string 19 | 20 | credentialsForm := huh.NewForm( 21 | huh.NewGroup( 22 | huh.NewInput(). 23 | Title("Encryption password"). 24 | Description("Used to decrypt the stored credentials.env file. Delete the file or enter nothing to enter new credentials."). 25 | Value(&encryptionPassword). 26 | EchoMode(huh.EchoModePassword), 27 | ), 28 | ) 29 | 30 | err := credentialsForm.Run() 31 | if err != nil { 32 | log.Fatal().Err(err).Msg("Credentials form error") 33 | } 34 | 35 | if encryptionPassword != "" { 36 | err = pbCredentials.Decrypt(encryptionPassword) 37 | if err != nil { 38 | log.Fatal().Err(err).Msg("Could not decrypt stored credentials") 39 | } 40 | 41 | return false 42 | } 43 | } else { 44 | err = pbCredentials.Load() 45 | if err != nil { 46 | log.Fatal().Err(err).Msg("Could not load stored credentials") 47 | } 48 | 49 | return false 50 | } 51 | } 52 | 53 | var storeCredentials bool 54 | 55 | form := huh.NewForm( 56 | huh.NewGroup( 57 | huh.NewInput(). 58 | Title("Hostname"). 59 | Value(&pbCredentials.Host), 60 | huh.NewInput(). 61 | Title("Email address"). 62 | Value(&pbCredentials.Email), 63 | huh.NewInput(). 64 | Title("Password"). 65 | Value(&pbCredentials.Password). 66 | EchoMode(huh.EchoModePassword), 67 | ), 68 | huh.NewGroup( 69 | huh.NewConfirm(). 70 | Title("Do you want to store the credentials?"). 71 | Value(&storeCredentials), 72 | ), 73 | ) 74 | 75 | err = form.Run() 76 | if err != nil { 77 | log.Fatal().Err(err).Msg("Form error") 78 | } 79 | 80 | return storeCredentials 81 | } 82 | 83 | func AskStoreCredentials(pbCredentials *credentials.Credentials) { 84 | var encryptCredentials bool 85 | 86 | useEncryptionForm := huh.NewForm( 87 | huh.NewGroup( 88 | huh.NewConfirm(). 89 | Title("Do you want to encrypt the credentials?"). 90 | Value(&encryptCredentials), 91 | ), 92 | ) 93 | 94 | err := useEncryptionForm.Run() 95 | if err != nil { 96 | log.Fatal().Err(err).Msg("Use encryption form error") 97 | } 98 | 99 | if encryptCredentials { 100 | var encryptionPassword string 101 | var encryptionPasswordRepeat string 102 | 103 | credentialsForm := huh.NewForm( 104 | huh.NewGroup( 105 | huh.NewInput(). 106 | Title("Encryption password"). 107 | Value(&encryptionPassword). 108 | EchoMode(huh.EchoModePassword). 109 | Validate(func(str string) error { 110 | if str == "" { 111 | return errors.New("password cannot be empty") 112 | } 113 | 114 | return nil 115 | }), 116 | huh.NewInput(). 117 | Title("Repeat encryption password"). 118 | Value(&encryptionPasswordRepeat). 119 | EchoMode(huh.EchoModePassword). 120 | Validate(func(str string) error { 121 | if str != encryptionPassword { 122 | return errors.New("passwords do not match") 123 | } 124 | 125 | return nil 126 | }), 127 | ), 128 | ) 129 | 130 | err = credentialsForm.Run() 131 | if err != nil { 132 | log.Fatal().Err(err).Msg("Form error") 133 | } 134 | 135 | err = pbCredentials.Encrypt(encryptionPassword) 136 | if err != nil { 137 | log.Fatal().Err(err).Msg("Encrypt error") 138 | } 139 | } else { 140 | err = pbCredentials.Save() 141 | if err != nil { 142 | log.Fatal().Err(err).Msg("Save error") 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /internal/forms/output.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | import ( 4 | "github.com/charmbracelet/huh" 5 | "github.com/rs/zerolog/log" 6 | ) 7 | 8 | func AskOutputTarget(inputValue string) string { 9 | var outputTarget string = inputValue 10 | 11 | form := huh.NewForm( 12 | huh.NewGroup( 13 | huh.NewInput(). 14 | Title("Output target"). 15 | Description("Target file for generated interfaces, keep empty to print results directly in console"). 16 | Value(&outputTarget), 17 | ), 18 | ) 19 | 20 | err := form.Run() 21 | if err != nil { 22 | log.Fatal().Err(err).Msg("Output target form error") 23 | } 24 | 25 | return outputTarget 26 | } 27 | -------------------------------------------------------------------------------- /internal/generator/property.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 6 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 7 | "github.com/iancoleman/strcase" 8 | "strings" 9 | ) 10 | 11 | type InterfacePropertyType int 12 | 13 | const ( 14 | IptString = iota 15 | IptNumber 16 | IptBoolean 17 | IptJson 18 | IptFile 19 | IptEnum 20 | IptRelation 21 | ) 22 | 23 | type InterfaceProperty struct { 24 | Name string 25 | CollectionName string 26 | Optional bool 27 | Type InterfacePropertyType 28 | IsArray bool 29 | Data interface{} 30 | } 31 | 32 | type CollectionWithProperties struct { 33 | Collection *pocketbase_api.Collection 34 | Properties []*InterfaceProperty 35 | } 36 | 37 | type propertyFlags struct { 38 | relationAsString bool 39 | forceOptional bool 40 | } 41 | 42 | func GetInterfacePropertyType(typeName string) InterfacePropertyType { 43 | switch typeName { 44 | case "number": 45 | return IptNumber 46 | case "bool": 47 | return IptBoolean 48 | case "select": 49 | return IptEnum 50 | case "json": 51 | return IptJson 52 | case "file": 53 | return IptFile 54 | case "relation": 55 | return IptRelation 56 | default: 57 | return IptString 58 | } 59 | } 60 | 61 | func (propertyType InterfacePropertyType) String() string { 62 | switch propertyType { 63 | case IptString: 64 | return "String" 65 | case IptNumber: 66 | return "Number" 67 | case IptBoolean: 68 | return "Boolean" 69 | case IptEnum: 70 | return "Enum" 71 | case IptJson: 72 | return "Json" 73 | case IptFile: 74 | return "File" 75 | case IptRelation: 76 | return "Relation" 77 | } 78 | 79 | return "Unknown" 80 | } 81 | 82 | func (property InterfaceProperty) String() string { 83 | var data = []string{ 84 | property.Type.String(), 85 | } 86 | 87 | if property.Optional { 88 | data = append(data, "Optional") 89 | } 90 | 91 | if property.IsArray { 92 | data = append(data, "Array") 93 | } 94 | 95 | if property.Type == IptRelation { 96 | relationTo, ok := property.Data.(string) 97 | if !ok { 98 | relationTo = "unknown (object)" 99 | } 100 | 101 | data = append(data, fmt.Sprintf("Relation to %s", relationTo)) 102 | } 103 | 104 | if property.Type == IptEnum { 105 | enumData := property.Data.([]string) 106 | 107 | data = append(data, fmt.Sprintf("Enum Data [%s]", strings.Join(enumData, ", "))) 108 | } 109 | 110 | return fmt.Sprintf("%s (%s)", property.Name, strings.Join(data, ", ")) 111 | } 112 | 113 | func (property InterfaceProperty) GetTypescriptProperty(generatorFlags *cmd.GeneratorFlags, flags propertyFlags) string { 114 | return fmt.Sprintf("%s: %s", property.getTypescriptName(generatorFlags, flags), property.getTypescriptTypeWithArray(flags)) 115 | } 116 | 117 | func (property InterfaceProperty) getTypescriptType(flags propertyFlags) string { 118 | switch property.Type { 119 | case IptNumber: 120 | return "number" 121 | case IptBoolean: 122 | return "boolean" 123 | case IptJson: 124 | if property.Optional { 125 | return "object | null | \"\"" 126 | } else { 127 | return "object" 128 | } 129 | case IptEnum: 130 | return strcase.ToCamel(fmt.Sprintf("%s_%s_%s", property.CollectionName, property.Name, "options")) 131 | case IptRelation: 132 | if flags.relationAsString { 133 | return "string" 134 | } 135 | 136 | relationTo, ok := property.Data.(string) 137 | if !ok { 138 | return "object" 139 | } else { 140 | return strcase.ToCamel(relationTo) 141 | } 142 | default: 143 | return "string" 144 | } 145 | } 146 | 147 | func (property InterfaceProperty) getTypescriptTypeWithArray(flags propertyFlags) string { 148 | tsType := property.getTypescriptType(flags) 149 | 150 | if property.IsArray { 151 | if property.Optional { 152 | return fmt.Sprintf("%s[]", tsType) 153 | } else { 154 | return fmt.Sprintf("[%s]", tsType) 155 | } 156 | } 157 | 158 | return tsType 159 | } 160 | 161 | func (property InterfaceProperty) getTypescriptName(generatorFlags *cmd.GeneratorFlags, flags propertyFlags) string { 162 | if property.Optional && generatorFlags.MakeNonRequiredOptional || flags.forceOptional { 163 | return fmt.Sprintf("%s?", property.Name) 164 | } 165 | 166 | return property.Name 167 | } 168 | 169 | func (collection CollectionWithProperties) GetTypescriptInterface(generatorFlags *cmd.GeneratorFlags) string { 170 | properties := make([]string, len(collection.Properties)) 171 | var additionalTypes []string 172 | var expandedRelations []string 173 | 174 | for i, property := range collection.Properties { 175 | properties[i] = fmt.Sprintf(" %s;", property.GetTypescriptProperty(generatorFlags, propertyFlags{forceOptional: false, relationAsString: true})) 176 | 177 | if property.Type == IptEnum { 178 | additionalTypes = append(additionalTypes, property.getTypescriptEnum()) 179 | } 180 | 181 | if property.Type == IptRelation { 182 | expandedRelations = append(expandedRelations, fmt.Sprintf(" %s;", property.GetTypescriptProperty(generatorFlags, propertyFlags{forceOptional: true, relationAsString: false}))) 183 | } 184 | } 185 | 186 | if len(expandedRelations) > 0 { 187 | expandedRelations = append(expandedRelations, " [key: string]: unknown;") 188 | 189 | expandedType := fmt.Sprintf("export interface %sExpanded {\n%s\n}", strcase.ToCamel(collection.Collection.Name), strings.Join(expandedRelations, "\n")) 190 | 191 | additionalTypes = append(additionalTypes, expandedType) 192 | 193 | expandedLine := fmt.Sprintf(" expand?: %sExpanded;", strcase.ToCamel(collection.Collection.Name)) 194 | 195 | properties = append([]string{expandedLine}, properties...) 196 | } else { 197 | expandedLine := " expand?: { [key: string]: unknown; };" 198 | 199 | properties = append([]string{expandedLine}, properties...) 200 | } 201 | 202 | prefix := strings.Join(additionalTypes, "\n\n") 203 | 204 | if prefix != "" { 205 | prefix += "\n\n" 206 | } 207 | 208 | return fmt.Sprintf("%sexport interface %s {\n%s\n}", prefix, strcase.ToCamel(collection.Collection.Name), strings.Join(properties, "\n")) 209 | } 210 | 211 | func (property InterfaceProperty) getTypescriptEnum() string { 212 | if property.Type != IptEnum { 213 | return "" 214 | } 215 | 216 | enumData := property.Data.([]string) 217 | enumName := strcase.ToCamel(fmt.Sprintf("%s_%s_%s", property.CollectionName, property.Name, "options")) 218 | 219 | enumList := make([]string, len(enumData)) 220 | 221 | for i, enum := range enumData { 222 | enumList[i] = fmt.Sprintf(" %s = \"%s\"", strcase.ToCamel(enum), enum) 223 | } 224 | 225 | return fmt.Sprintf("export enum %s {\n%s\n}", enumName, strings.Join(enumList, ",\n")) 226 | } 227 | -------------------------------------------------------------------------------- /internal/interpreter/property.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/generator" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 6 | ) 7 | 8 | func InterpretCollections(collections []*pocketbase_api.Collection, allCollections []pocketbase_api.Collection) []*generator.CollectionWithProperties { 9 | output := make([]*generator.CollectionWithProperties, len(collections)) 10 | 11 | for i, collection := range collections { 12 | output[i] = InterpretCollection(collection, allCollections) 13 | } 14 | 15 | return output 16 | } 17 | 18 | func InterpretCollection(collection *pocketbase_api.Collection, allCollections []pocketbase_api.Collection) *generator.CollectionWithProperties { 19 | output := &generator.CollectionWithProperties{ 20 | Collection: collection, 21 | } 22 | 23 | for _, field := range collection.Fields { 24 | if field.Hidden { 25 | continue 26 | } 27 | 28 | output.Properties = append(output.Properties, InterpretProperty(field, collection, allCollections)) 29 | } 30 | 31 | return output 32 | } 33 | 34 | func InterpretProperty(field pocketbase_api.CollectionField, collection *pocketbase_api.Collection, allCollections []pocketbase_api.Collection) *generator.InterfaceProperty { 35 | output := &generator.InterfaceProperty{ 36 | Name: field.Name, 37 | CollectionName: collection.Name, 38 | Type: generator.GetInterfacePropertyType(field.Type), 39 | Optional: !field.Required, 40 | } 41 | 42 | if output.Type == generator.IptEnum || output.Type == generator.IptRelation || output.Type == generator.IptFile { 43 | output.IsArray = field.MaxSelect > 1 44 | } 45 | 46 | if output.Type == generator.IptRelation { 47 | output.Data = nil 48 | 49 | for _, collection := range allCollections { 50 | if collection.Id == field.CollectionId { 51 | output.Data = collection.Name 52 | break 53 | } 54 | } 55 | } 56 | 57 | if output.Type == generator.IptEnum { 58 | data := make([]string, len(field.Values)) 59 | 60 | for i, value := range field.Values { 61 | data[i] = value 62 | } 63 | 64 | output.Data = data 65 | } 66 | 67 | return output 68 | } 69 | -------------------------------------------------------------------------------- /internal/pocketbase_api/api.go: -------------------------------------------------------------------------------- 1 | package pocketbase_api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/Vogeslu/pocketbase-ts-generator/internal/credentials" 9 | "github.com/rs/zerolog/log" 10 | "io" 11 | "net/http" 12 | ) 13 | 14 | type PocketBase struct { 15 | credentials *credentials.Credentials 16 | token string 17 | client *http.Client 18 | } 19 | 20 | func New(Credentials *credentials.Credentials) *PocketBase { 21 | pocketBase := &PocketBase{ 22 | credentials: Credentials, 23 | client: &http.Client{}, 24 | } 25 | 26 | return pocketBase 27 | } 28 | 29 | func (pocketBase *PocketBase) GetApiUrl(suffix string) string { 30 | return fmt.Sprintf("%s/api/%s", pocketBase.credentials.Host, suffix) 31 | } 32 | 33 | type pocketBaseAuthResponse struct { 34 | Token string `json:"token"` 35 | } 36 | 37 | func (pocketBase *PocketBase) Authenticate() error { 38 | log.Info().Msgf("Authenticating with %s...", pocketBase.credentials.Host) 39 | 40 | body := []byte(fmt.Sprintf(`{ 41 | "identity": "%s", 42 | "password": "%s" 43 | }`, pocketBase.credentials.Email, pocketBase.credentials.Password)) 44 | 45 | request, err := http.NewRequest("POST", pocketBase.GetApiUrl("collections/_superusers/auth-with-password"), bytes.NewBuffer(body)) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | request.Header.Add("Content-Type", "application/json") 51 | 52 | response, err := pocketBase.client.Do(request) 53 | if err != nil { 54 | return err 55 | } 56 | defer func(Body io.ReadCloser) { 57 | err := Body.Close() 58 | if err != nil { 59 | fmt.Println(err) 60 | } 61 | }(response.Body) 62 | 63 | log.Debug().Msgf("Got status code %d", response.StatusCode) 64 | 65 | if response.StatusCode != http.StatusOK { 66 | return errors.New("invalid status code, expected 200") 67 | } 68 | 69 | authResponse := &pocketBaseAuthResponse{} 70 | err = json.NewDecoder(response.Body).Decode(authResponse) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | if authResponse.Token == "" { 76 | return errors.New("token is missing") 77 | } 78 | 79 | log.Debug().Msgf("Got token %s", authResponse.Token) 80 | 81 | log.Info().Msgf("Authentication successful") 82 | 83 | pocketBase.token = authResponse.Token 84 | 85 | return nil 86 | } 87 | 88 | func (pocketBase *PocketBase) DoWithAuth(request *http.Request) (*http.Response, error) { 89 | if pocketBase.token != "" { 90 | request.Header.Set("Authorization", pocketBase.token) 91 | } 92 | 93 | return pocketBase.client.Do(request) 94 | } 95 | -------------------------------------------------------------------------------- /internal/pocketbase_api/collections.go: -------------------------------------------------------------------------------- 1 | package pocketbase_api 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | type CollectionField struct { 12 | Id string `json:"id"` 13 | Name string `json:"name"` 14 | Type string `json:"type"` 15 | CollectionId string `json:"collectionId"` 16 | MaxSelect int `json:"maxSelect"` 17 | Required bool `json:"required"` 18 | Hidden bool `json:"hidden"` 19 | Values []string `json:"values"` 20 | } 21 | 22 | type Collection struct { 23 | Id string `json:"id"` 24 | Name string `json:"name"` 25 | Type string `json:"type"` 26 | System bool `json:"system"` 27 | Fields []CollectionField `json:"fields"` 28 | } 29 | 30 | type CollectionsResponse struct { 31 | Items []Collection `json:"items"` 32 | } 33 | 34 | func (pocketBase *PocketBase) GetCollections() (*CollectionsResponse, error) { 35 | request, err := http.NewRequest("GET", pocketBase.GetApiUrl("collections?perPage=500"), nil) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | response, err := pocketBase.DoWithAuth(request) 41 | if err != nil { 42 | return nil, err 43 | } 44 | defer func(Body io.ReadCloser) { 45 | err := Body.Close() 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | }(response.Body) 50 | 51 | if response.StatusCode != http.StatusOK { 52 | return nil, errors.New("invalid status code, expected 200") 53 | } 54 | 55 | collectionResponse := &CollectionsResponse{} 56 | err = json.NewDecoder(response.Body).Decode(collectionResponse) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return collectionResponse, nil 62 | } 63 | 64 | func (collection Collection) String() string { 65 | if collection.System { 66 | return fmt.Sprintf("%s (System, %d fields)", collection.Name, len(collection.Fields)) 67 | } 68 | 69 | return fmt.Sprintf("%s (%d fields)", collection.Name, len(collection.Fields)) 70 | } 71 | -------------------------------------------------------------------------------- /internal/pocketbase_core/collections.go: -------------------------------------------------------------------------------- 1 | package pocketbase_core 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 5 | "github.com/pocketbase/pocketbase" 6 | ) 7 | 8 | func GetCollections(app *pocketbase.PocketBase) (*pocketbase_api.CollectionsResponse, error) { 9 | pbCollections, err := app.App.FindAllCollections() 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | output := &pocketbase_api.CollectionsResponse{ 15 | Items: convertPBCollections(pbCollections), 16 | } 17 | 18 | return output, nil 19 | } 20 | -------------------------------------------------------------------------------- /internal/pocketbase_core/converter.go: -------------------------------------------------------------------------------- 1 | package pocketbase_core 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 5 | "github.com/pocketbase/pocketbase/core" 6 | ) 7 | 8 | func convertPBCollections(pbCollections []*core.Collection) []pocketbase_api.Collection { 9 | output := make([]pocketbase_api.Collection, len(pbCollections)) 10 | 11 | for i, pbCollection := range pbCollections { 12 | output[i] = convertPBCollection(pbCollection) 13 | } 14 | 15 | return output 16 | } 17 | 18 | func convertPBCollection(pbCollection *core.Collection) pocketbase_api.Collection { 19 | return pocketbase_api.Collection{ 20 | Id: pbCollection.Id, 21 | Name: pbCollection.Name, 22 | Type: pbCollection.Type, 23 | System: pbCollection.System, 24 | Fields: convertPBFields(pbCollection.Fields), 25 | } 26 | } 27 | 28 | func convertPBFields(pbFields core.FieldsList) []pocketbase_api.CollectionField { 29 | var output []pocketbase_api.CollectionField 30 | 31 | for _, pbField := range pbFields { 32 | output = append(output, convertPBField(pbField)) 33 | } 34 | 35 | return output 36 | } 37 | 38 | func convertPBField(pbField core.Field) pocketbase_api.CollectionField { 39 | field := pocketbase_api.CollectionField{ 40 | Id: pbField.GetId(), 41 | Name: pbField.GetName(), 42 | Type: pbField.Type(), 43 | Hidden: pbField.GetHidden(), 44 | } 45 | 46 | switch v := pbField.(type) { 47 | case *core.TextField: 48 | field.Required = v.Required 49 | case *core.EditorField: 50 | field.Required = v.Required 51 | case *core.NumberField: 52 | field.Required = v.Required 53 | case *core.BoolField: 54 | field.Required = v.Required 55 | case *core.EmailField: 56 | field.Required = v.Required 57 | case *core.URLField: 58 | field.Required = v.Required 59 | case *core.DateField: 60 | field.Required = v.Required 61 | case *core.SelectField: 62 | field.MaxSelect = v.MaxSelect 63 | field.Required = v.Required 64 | field.Values = v.Values 65 | case *core.FileField: 66 | field.MaxSelect = v.MaxSelect 67 | field.Required = v.Required 68 | case *core.RelationField: 69 | field.MaxSelect = v.MaxSelect 70 | field.Required = v.Required 71 | field.CollectionId = v.CollectionId 72 | } 73 | 74 | return field 75 | } 76 | -------------------------------------------------------------------------------- /pkg/pocketbase-ts-generator/cmd.go: -------------------------------------------------------------------------------- 1 | package pocketbase_ts_generator 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 5 | "github.com/pocketbase/pocketbase" 6 | "github.com/rs/zerolog/log" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func RegisterCommand(app *pocketbase.PocketBase) { 11 | app.RootCmd.AddCommand(cmd.GetGenerateTsCommand(true, func(cmd *cobra.Command, args []string, generatorFlags *cmd.GeneratorFlags) { 12 | err := processFileGeneration(app, generatorFlags) 13 | if err != nil { 14 | log.Fatal().Err(err).Msg("Could not process file generation") 15 | } 16 | })) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/pocketbase-ts-generator/core.go: -------------------------------------------------------------------------------- 1 | package pocketbase_ts_generator 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 5 | "github.com/Vogeslu/pocketbase-ts-generator/internal/core" 6 | "github.com/Vogeslu/pocketbase-ts-generator/internal/forms" 7 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_api" 8 | "github.com/Vogeslu/pocketbase-ts-generator/internal/pocketbase_core" 9 | "github.com/pocketbase/pocketbase" 10 | ) 11 | 12 | func processFileGeneration(app *pocketbase.PocketBase, generatorFlags *cmd.GeneratorFlags) error { 13 | collections, err := pocketbase_core.GetCollections(app) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | var selectedCollections []*pocketbase_api.Collection 19 | 20 | selectedCollections = forms.GetSelectedCollections(generatorFlags, collections.Items) 21 | 22 | core.ProcessCollections(selectedCollections, collections.Items, generatorFlags) 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/pocketbase-ts-generator/hook.go: -------------------------------------------------------------------------------- 1 | package pocketbase_ts_generator 2 | 3 | import ( 4 | "github.com/Vogeslu/pocketbase-ts-generator/internal/cmd" 5 | "github.com/pocketbase/pocketbase" 6 | pbcore "github.com/pocketbase/pocketbase/core" 7 | ) 8 | 9 | type GeneratorOptions struct { 10 | AllCollections bool 11 | CollectionsInclude []string 12 | CollectionsExclude []string 13 | 14 | Output string 15 | } 16 | 17 | func RegisterHook(app *pocketbase.PocketBase, options *GeneratorOptions) { 18 | generatorFlags := &cmd.GeneratorFlags{ 19 | AllCollections: options.AllCollections, 20 | CollectionsInclude: options.CollectionsInclude, 21 | CollectionsExclude: options.CollectionsExclude, 22 | 23 | Output: options.Output, 24 | } 25 | 26 | app.OnCollectionAfterCreateSuccess().BindFunc(func(e *pbcore.CollectionEvent) error { 27 | _ = processFileGeneration(app, generatorFlags) 28 | 29 | return e.Next() 30 | }) 31 | 32 | app.OnCollectionAfterUpdateSuccess().BindFunc(func(e *pbcore.CollectionEvent) error { 33 | _ = processFileGeneration(app, generatorFlags) 34 | 35 | return e.Next() 36 | }) 37 | 38 | app.OnCollectionAfterDeleteSuccess().BindFunc(func(e *pbcore.CollectionEvent) error { 39 | _ = processFileGeneration(app, generatorFlags) 40 | 41 | return e.Next() 42 | }) 43 | } 44 | --------------------------------------------------------------------------------