├── .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 |
--------------------------------------------------------------------------------