Merge pull request #20 from foomo/feature/v0.2.x

Release v0.2.x
This commit is contained in:
Kevin Franklin Kim 2024-05-30 15:21:17 +02:00 committed by GitHub
commit ec29972261
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1750 additions and 653 deletions

View File

@ -8,7 +8,7 @@ on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true
jobs:

View File

@ -74,9 +74,11 @@ linters-settings:
- name: struct-tag
arguments:
- "json,inline"
- "yaml,squash"
- name: unhandled-error
arguments:
- "fmt.Println"
- "strings.Builder.WriteString"
# TODO remove
- name: deep-exit
disabled: true

View File

@ -27,12 +27,7 @@ doc:
.PHONY: test
## Run tests
test:
@go test -coverprofile=coverage.out -race -json ./... | gotestfmt
.PHONY: test.update
## Run tests and update snapshots
test.update:
@go test -update -coverprofile=coverage.out -race -json ./... | gotestfmt
@GO_TEST_TAGS=-skip go test -coverprofile=coverage.out -race -json ./... | gotestfmt
.PHONY: lint
## Run linter
@ -58,6 +53,10 @@ outdated:
install:
@go build -o ${GOPATH}/bin/sesamy main.go
## Install debug binary
install.debug:
@go build -gcflags "all=-N -l" -o ${GOPATH}/bin/sesamy main.go
## Build binary
build:
@mkdir -p bin

View File

@ -31,23 +31,99 @@ google:
gtm:
account_id: 6099238525
server:
container_id: 175348980
workspace_id: 10
measurement_id: GTM-5NWPR4QW
web:
container_id: 175355532
workspace_id: 23
measurement_id: GTM-57BHX34G
workspace_id: 23
server:
container_id: 175348980
measurement_id: GTM-5NWPR4QW
workspace_id: 10
credentials_file: ./tmp/google_service_account_creds.json
request_quota: 15
credentials_file: ./google_service_account_creds.json
events:
typescript:
packages:
- path: "github.com/foomo/sesamy-cli/_example/server"
output_path: "./_example/client/types.d.ts"
indent: "\t"
- path: 'github.com/username/repository/event'
types:
- Custom
- path: 'github.com/foomo/sesamy-go/event'
types:
- PageView
- SelectItem
output_path: '/path/to/src'
tagmanager:
tags:
ga4_enabled: true
packages:
- path: 'github.com/username/repository/event'
types:
- Custom
- path: 'github.com/foomo/sesamy-go/event'
types:
- AddPaymentInfo
- AddShippingInfo
- AddToCart
- AddToWishlist
- AdImpression
- BeginCheckout
- CampaignDetails
- Click
- EarnVirtualMoney
- FileDownload
- FormStart
- FormSubmit
- GenerateLead
- JoinGroup
- LevelEnd
- LevelStart
- LevelUp
- Login
- PageView
- PostScore
- Purchase
- Refund
- RemoveFromCart
- ScreenView
- Scroll
- Search
- SelectContent
- SelectItem
- SelectPromotion
- SessionStart
- Share
- SignUp
- SpendVirtualCurrency
- TutorialBegin
- TutorialComplete
- UnlockAchievement
- UserEngagement
- VideoComplete
- VideoProgress
- VideoStart
- ViewCart
- ViewItem
- ViewItemList
- ViewPromotion
- ViewSearchResults
prefixes:
client: ''
folder: ''
tags:
ga4_event: 'GA4 - '
google_tag: ''
server_ga4_event: 'GA4 - '
triggers:
client: ''
custom_event: 'Event - '
variables:
constant: ''
event_model: 'dlv.eventModel.'
gt_event_settings: 'Event Settings - '
gt_settings: 'Settings - '
```
## Caveats

View File

@ -1,55 +0,0 @@
// Code generated by tygo. DO NOT EDIT.
//////////
// source: addtocart.go
export interface AddToCart {
currency?: string;
value?: number /* float64 */;
items?: (Item | undefined)[];
}
//////////
// source: item.go
export interface Item {
affiliation?: string;
coupon?: string;
discount?: number /* float64 */;
index?: number /* int */;
item_brand?: string;
item_category?: string;
item_category2?: string;
item_category3?: string;
item_category4?: string;
item_category5?: string;
item_id?: string;
item_list_name?: string;
item_name?: string;
item_variant?: string;
item_list_id?: string;
location_id?: string;
price?: string;
quantity?: number /* float64 */;
}
//////////
// source: login.go
export interface Login {
method?: string;
}
//////////
// source: search.go
export interface Search {
search_term?: string;
}
//////////
// source: signup.go
export interface SignUp {
method?: string;
}

View File

@ -1,7 +0,0 @@
package server
type AddToCart struct {
Currency string `json:"currency,omitempty"`
Value float64 `json:"value,omitempty"`
Items []*Item `json:"items,omitempty"`
}

View File

@ -1,22 +0,0 @@
package server
type Item struct {
Affiliation string `json:"affiliation,omitempty"`
Coupon string `json:"coupon,omitempty"`
Discount float64 `json:"discount,omitempty"`
Index int `json:"index,omitempty"`
ItemBrand string `json:"item_brand,omitempty"`
ItemCategory string `json:"item_category,omitempty"`
ItemCategory2 string `json:"item_category2,omitempty"`
ItemCategory3 string `json:"item_category3,omitempty"`
ItemCategory4 string `json:"item_category4,omitempty"`
ItemCategory5 string `json:"item_category5,omitempty"`
ItemID string `json:"item_id,omitempty"`
ItemListName string `json:"item_list_name,omitempty"`
ItemName string `json:"item_name,omitempty"`
ItemVariant string `json:"item_variant,omitempty"`
ItemListID string `json:"item_list_id,omitempty"`
LocationID string `json:"location_id,omitempty"`
Price string `json:"price,omitempty"`
Quantity float64 `json:"quantity,omitempty"`
}

View File

@ -1,5 +0,0 @@
package server
type Login struct {
Method string `json:"method,omitempty"`
}

View File

@ -1,5 +0,0 @@
package server
type Search struct {
SearchTerm string `json:"search_term,omitempty"`
}

View File

@ -1,5 +0,0 @@
package server
type SignUp struct {
Method string `json:"method,omitempty"`
}

View File

@ -4,22 +4,20 @@ import (
"github.com/spf13/cobra"
)
// tagmanagerCmd represents the tagmanager command
var tagmanagerCmd = &cobra.Command{
Use: "tagmanager",
Short: "Provision Google Tag Manager containers",
// NewTagmanagerCmd represents the tagmanager command
func NewTagmanagerCmd(root *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "tagmanager",
Short: "Provision Google Tag Manager containers",
}
root.AddCommand(cmd)
return cmd
}
func init() {
rootCmd.AddCommand(tagmanagerCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// tagmanagerCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// tagmanagerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
cmd := NewTagmanagerCmd(rootCmd)
NewTagManagerServerCmd(cmd)
NewTagmanagerWebCmd(cmd)
}

View File

@ -11,110 +11,116 @@ import (
tagmanager2 "google.golang.org/api/tagmanager/v2"
)
// tagmanagerServerCmd represents the server command
// NewTagManagerServerCmd represents the server command
// TODO flags workspace, dry, diff
// TODO google user auth
var tagmanagerServerCmd = &cobra.Command{
Use: "server",
Short: "Provision Google Tag Manager Server Container",
PersistentPreRunE: preRunReadConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var clientCredentialsOption option.ClientOption
if cfg.Google.CredentialsFile != "" {
clientCredentialsOption = option.WithCredentialsFile(cfg.Google.CredentialsFile)
} else {
clientCredentialsOption = option.WithCredentialsJSON([]byte(cfg.Google.CredentialsJSON))
}
func NewTagManagerServerCmd(root *cobra.Command) {
var cmd = &cobra.Command{
Use: "server",
Short: "Provision Google Tag Manager Server Container",
PersistentPreRunE: preRunReadConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var clientCredentialsOption option.ClientOption
if cfg.Google.CredentialsFile != "" {
clientCredentialsOption = option.WithCredentialsFile(cfg.Google.CredentialsFile)
} else {
clientCredentialsOption = option.WithCredentialsJSON([]byte(cfg.Google.CredentialsJSON))
}
c, err := tagmanager.NewClient(
cmd.Context(),
logger,
cfg.Google.GTM.AccountID,
cfg.Google.GTM.Server.ContainerID,
cfg.Google.GTM.Server.WorkspaceID,
cfg.Google.GA4.MeasurementID,
tagmanager.ClientWithRequestQuota(cfg.Google.RequestQuota),
tagmanager.ClientWithClientOptions(clientCredentialsOption),
)
if err != nil {
return err
}
p := cfg.Tagmanager.Prefixes
{
name := p.FolderName(c.FolderName())
if _, err := c.UpsertFolder(name); err != nil {
c, err := tagmanager.NewClient(
cmd.Context(),
logger,
cfg.Google.GTM.AccountID,
cfg.Google.GTM.Server.ContainerID,
cfg.Google.GTM.Server.WorkspaceID,
cfg.Google.GA4.MeasurementID,
tagmanager.ClientWithRequestQuota(cfg.Google.RequestQuota),
tagmanager.ClientWithClientOptions(clientCredentialsOption),
)
if err != nil {
return err
}
}
{
if _, err := c.EnableBuiltInVariable("clientName"); err != nil {
return err
p := cfg.Tagmanager.Prefixes
{
name := p.FolderName(c.FolderName())
if _, err := c.UpsertFolder(name); err != nil {
return err
}
}
}
var ga4MeasurementID *tagmanager2.Variable
{
name := p.Variables.ConstantName("Google Analytics GA4 ID")
if ga4MeasurementID, err = c.UpsertVariable(variable.NewConstant(name, c.MeasurementID())); err != nil {
return err
{
if _, err := c.EnableBuiltInVariable("clientName"); err != nil {
return err
}
}
}
var webContainerMeasurementID *tagmanager2.Variable
{
name := p.Variables.ConstantName("Google Tag Mangager Web Container ID")
if webContainerMeasurementID, err = c.UpsertVariable(variable.NewConstant(name, cfg.Google.GTM.Web.MeasurementID)); err != nil {
return err
var ga4MeasurementID *tagmanager2.Variable
{
name := p.Variables.ConstantName("Google Analytics GA4 ID")
if ga4MeasurementID, err = c.UpsertVariable(variable.NewConstant(name, c.MeasurementID())); err != nil {
return err
}
}
}
{
name := p.ClientName("Google Tag Manager Web Container")
if _, err := c.UpsertClient(client.NewGTM(name, webContainerMeasurementID)); err != nil {
return err
var webContainerMeasurementID *tagmanager2.Variable
{
name := p.Variables.ConstantName("Google Tag Mangager Web Container ID")
if webContainerMeasurementID, err = c.UpsertVariable(variable.NewConstant(name, cfg.Google.GTM.Web.MeasurementID)); err != nil {
return err
}
}
}
var ga4Client *tagmanager2.Client
{
name := p.ClientName("Google Analytics GA4")
if ga4Client, err = c.UpsertClient(client.NewGA4(name)); err != nil {
return err
{
name := p.ClientName("Google Tag Manager Web Container")
if _, err := c.UpsertClient(client.NewGTM(name, webContainerMeasurementID)); err != nil {
return err
}
}
}
var ga4ClientTrigger *tagmanager2.Trigger
{
name := p.Triggers.ClientName("Google Analytics GA4 Client")
if ga4ClientTrigger, err = c.UpsertTrigger(trigger.NewClient(name, ga4Client)); err != nil {
return err
var mpv2Client *tagmanager2.Client
{
name := p.ClientName("Measurement Protocol GA4")
if mpv2Client, err = c.UpsertClient(client.NewMPv2(name)); err != nil {
return err
}
}
}
{
name := p.Tags.ServerGA4EventName("Google Analytics GA4")
if _, err := c.UpsertTag(client2.NewServerGA4Event(name, ga4MeasurementID, ga4ClientTrigger)); err != nil {
return err
var mpv2ClientTrigger *tagmanager2.Trigger
{
name := p.Triggers.ClientName("Measurement Protocol GA4 Client")
if mpv2ClientTrigger, err = c.UpsertTrigger(trigger.NewClient(name, mpv2Client)); err != nil {
return err
}
}
}
return nil
},
}
func init() {
tagmanagerCmd.AddCommand(tagmanagerServerCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// tagmanagerServerCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// tagmanagerServerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
var ga4Client *tagmanager2.Client
{
name := p.ClientName("Google Analytics GA4")
if ga4Client, err = c.UpsertClient(client.NewGA4(name)); err != nil {
return err
}
}
var ga4ClientTrigger *tagmanager2.Trigger
{
name := p.Triggers.ClientName("Google Analytics GA4 Client")
if ga4ClientTrigger, err = c.UpsertTrigger(trigger.NewClient(name, ga4Client)); err != nil {
return err
}
}
{
name := p.Tags.ServerGA4EventName("Google Analytics GA4")
if _, err := c.UpsertTag(client2.NewServerGA4Event(name, ga4MeasurementID, ga4ClientTrigger, mpv2ClientTrigger)); err != nil {
return err
}
}
return nil
},
}
root.AddCommand(cmd)
}

View File

@ -1,9 +1,11 @@
package cmd
import (
"go/types"
"strconv"
"github.com/foomo/sesamy-cli/internal"
"github.com/foomo/gocontemplate/pkg/assume"
"github.com/foomo/gocontemplate/pkg/contemplate"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
client "github.com/foomo/sesamy-cli/pkg/tagmanager/tag"
trigger2 "github.com/foomo/sesamy-cli/pkg/tagmanager/trigger"
@ -13,130 +15,143 @@ import (
tagmanager2 "google.golang.org/api/tagmanager/v2"
)
// tagmanagerWebCmd represents the web command
var tagmanagerWebCmd = &cobra.Command{
Use: "web",
Short: "Provision Google Tag Manager Web Container",
PersistentPreRunE: preRunReadConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var clientCredentialsOption option.ClientOption
if cfg.Google.CredentialsFile != "" {
clientCredentialsOption = option.WithCredentialsFile(cfg.Google.CredentialsFile)
} else {
clientCredentialsOption = option.WithCredentialsJSON([]byte(cfg.Google.CredentialsJSON))
}
eventParameters, err := internal.GetEventParameters(cfg.Tagmanager)
if err != nil {
return err
}
c, err := tagmanager.NewClient(
cmd.Context(),
logger,
cfg.Google.GTM.AccountID,
cfg.Google.GTM.Web.ContainerID,
cfg.Google.GTM.Web.WorkspaceID,
cfg.Google.GA4.MeasurementID,
tagmanager.ClientWithRequestQuota(cfg.Google.RequestQuota),
tagmanager.ClientWithClientOptions(clientCredentialsOption),
)
if err != nil {
return err
}
p := cfg.Tagmanager.Prefixes
if _, err := c.UpsertFolder(p.FolderName(c.FolderName())); err != nil {
return err
}
var ga4MeasurementID *tagmanager2.Variable
{
name := p.Variables.ConstantName("Google Analytics GA4 ID")
if ga4MeasurementID, err = c.UpsertVariable(variable.NewConstant(name, c.MeasurementID())); err != nil {
return err
// NewTagmanagerWebCmd represents the web command
func NewTagmanagerWebCmd(root *cobra.Command) {
getEventParams := func(obj types.Object) []string {
var ret []string
if eventStruct := assume.T[*types.Struct](obj.Type().Underlying()); eventStruct != nil {
for i := range eventStruct.NumFields() {
if eventField := eventStruct.Field(i); eventField.Name() == "Params" {
if paramsStruct := assume.T[*types.Struct](eventField.Type().Underlying()); paramsStruct != nil {
for j := range paramsStruct.NumFields() {
ret = append(ret, paramsStruct.Field(j).Name())
}
}
}
}
}
return ret
}
var serverContainerURL *tagmanager2.Variable
{
name := p.Variables.ConstantName("Server Container URL")
if serverContainerURL, err = c.UpsertVariable(variable.NewConstant(name, cfg.Google.GT.ServerContainerURL)); err != nil {
cmd := &cobra.Command{
Use: "web",
Short: "Provision Google Tag Manager Web Container",
PersistentPreRunE: preRunReadConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var clientCredentialsOption option.ClientOption
if cfg.Google.CredentialsFile != "" {
clientCredentialsOption = option.WithCredentialsFile(cfg.Google.CredentialsFile)
} else {
clientCredentialsOption = option.WithCredentialsJSON([]byte(cfg.Google.CredentialsJSON))
}
parser, err := contemplate.Load(&cfg.Tagmanager.Config)
if err != nil {
return err
}
}
var googleTagSettings *tagmanager2.Variable
{
name := p.Variables.GTSettingsName("Google Tag")
if googleTagSettings, err = c.UpsertVariable(variable.NewGTSettings(name, map[string]*tagmanager2.Variable{
"server_container_url": serverContainerURL,
})); err != nil {
eventParameters := map[string][]string{}
for _, pkgCfg := range cfg.Tagmanager.Packages {
pkg := parser.Package(pkgCfg.Path)
for _, event := range pkgCfg.Types {
eventParameters[event] = getEventParams(pkg.LookupScopeType(event))
}
}
c, err := tagmanager.NewClient(
cmd.Context(),
logger,
cfg.Google.GTM.AccountID,
cfg.Google.GTM.Web.ContainerID,
cfg.Google.GTM.Web.WorkspaceID,
cfg.Google.GA4.MeasurementID,
tagmanager.ClientWithRequestQuota(cfg.Google.RequestQuota),
tagmanager.ClientWithClientOptions(clientCredentialsOption),
)
if err != nil {
return err
}
}
{
name := p.Tags.GoogleTagName("Google Tag")
if _, err = c.UpsertTag(client.NewGoogleTag(name, ga4MeasurementID, googleTagSettings, map[string]string{
"enable_page_views": strconv.FormatBool(cfg.Google.GT.EnablePageViews),
})); err != nil {
p := cfg.Tagmanager.Prefixes
if _, err := c.UpsertFolder(p.FolderName(c.FolderName())); err != nil {
return err
}
}
for event, parameters := range eventParameters {
var trigger *tagmanager2.Trigger
var ga4MeasurementID *tagmanager2.Variable
{
name := p.Triggers.CustomEventName(event)
if trigger, err = c.UpsertTrigger(trigger2.NewCustomEvent(name, event)); err != nil {
name := p.Variables.ConstantName("Google Analytics GA4 ID")
if ga4MeasurementID, err = c.UpsertVariable(variable.NewConstant(name, c.MeasurementID())); err != nil {
return err
}
}
eventSettingsVariables := make(map[string]*tagmanager2.Variable, len(parameters))
for _, parameter := range parameters {
name := p.Variables.EventModelName(parameter)
eventSettingsVariables[parameter], err = c.UpsertVariable(variable.NewEventModel(name, parameter))
if err != nil {
var serverContainerURL *tagmanager2.Variable
{
name := p.Variables.ConstantName("Server Container URL")
if serverContainerURL, err = c.UpsertVariable(variable.NewConstant(name, cfg.Google.GT.ServerContainerURL)); err != nil {
return err
}
}
var eventSettings *tagmanager2.Variable
var googleTagSettings *tagmanager2.Variable
{
name := p.Variables.GTEventSettingsName(event)
if eventSettings, err = c.UpsertVariable(variable.NewGTEventSettings(name, eventSettingsVariables)); err != nil {
name := p.Variables.GTSettingsName("Google Tag")
if googleTagSettings, err = c.UpsertVariable(variable.NewGTSettings(name, map[string]*tagmanager2.Variable{
"server_container_url": serverContainerURL,
})); err != nil {
return err
}
}
{
name := p.Tags.GA4EventName(event)
if _, err := c.UpsertTag(client.NewGA4Event(name, event, eventSettings, ga4MeasurementID, trigger)); err != nil {
name := p.Tags.GoogleTagName("Google Tag")
if _, err = c.UpsertTag(client.NewGoogleTag(name, ga4MeasurementID, googleTagSettings, map[string]string{
"enable_page_views": strconv.FormatBool(cfg.Google.GT.EnablePageViews),
})); err != nil {
return err
}
}
}
return nil
},
}
var filter string
func init() {
tagmanagerCmd.AddCommand(tagmanagerWebCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// tagmanagerWebCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// tagmanagerWebCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
tagmanagerWebCmd.Flags().StringVar(&filter, "filter", "", "Filter tag manager")
for event, parameters := range eventParameters {
var trigger *tagmanager2.Trigger
{
name := p.Triggers.CustomEventName(event)
if trigger, err = c.UpsertTrigger(trigger2.NewCustomEvent(name, event)); err != nil {
return err
}
}
if cfg.Tagmanager.Tags.GA4Enabled {
eventSettingsVariables := make(map[string]*tagmanager2.Variable, len(parameters))
for _, parameter := range parameters {
name := p.Variables.EventModelName(parameter)
eventSettingsVariables[parameter], err = c.UpsertVariable(variable.NewEventModel(name, parameter))
if err != nil {
return err
}
}
var eventSettings *tagmanager2.Variable
{
name := p.Variables.GTEventSettingsName(event)
if eventSettings, err = c.UpsertVariable(variable.NewGTEventSettings(name, eventSettingsVariables)); err != nil {
return err
}
}
{
name := p.Tags.GA4EventName(event)
if _, err := c.UpsertTag(client.NewGA4Event(name, event, eventSettings, ga4MeasurementID, trigger)); err != nil {
return err
}
}
}
}
return nil
},
}
root.AddCommand(cmd)
}

View File

@ -1,8 +1,15 @@
package cmd
import (
"github.com/gzuidhof/tygo/tygo"
"os"
"path"
"path/filepath"
"github.com/foomo/gocontemplate/pkg/contemplate"
"github.com/foomo/sesamy-cli/pkg/typescript/generator"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/exp/maps"
)
// typescriptCmd represents the typescript command
@ -11,27 +18,36 @@ var typescriptCmd = &cobra.Command{
Short: "Generate typescript events",
PersistentPreRunE: preRunReadConfig,
RunE: func(cmd *cobra.Command, args []string) error {
gen := tygo.New(&tygo.Config{
Packages: cfg.Typescript.Packages,
})
for k, v := range cfg.Typescript.TypeMappings {
gen.SetTypeMapping(k, v)
ctpl, err := contemplate.Load(&cfg.Typescript.Config)
if err != nil {
return err
}
return gen.Generate()
files, err := generator.Generate(logger, ctpl)
if err != nil {
return errors.Wrap(err, "failed to get build typescript")
}
outPath, err := filepath.Abs(cfg.Typescript.OutputPath)
if err != nil {
return errors.Wrap(err, "failed to get output path")
}
if err = os.MkdirAll(outPath, os.ModePerm); err != nil {
return errors.Wrap(err, "failed to create typescript output directory")
}
logger.InfoContext(cmd.Context(), "generated typescript code", "dir", outPath, "files", maps.Keys(files))
for filename, file := range files {
if err = os.WriteFile(path.Join(outPath, filename), []byte(file.String()), 0600); err != nil {
return errors.Wrap(err, "failed to write typescript code")
}
}
return nil
},
}
func init() {
rootCmd.AddCommand(typescriptCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// typescriptCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// typescriptCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@ -8,14 +8,18 @@ import (
var version = "latest"
var versionCmd = &cobra.Command{
Use: "version",
Short: "show version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version)
},
func NewVersionCmd(root *cobra.Command) {
cmd := &cobra.Command{
Use: "version",
Short: "show version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version)
},
}
root.AddCommand(cmd)
}
func init() {
rootCmd.AddCommand(versionCmd)
NewVersionCmd(rootCmd)
}

23
go.mod
View File

@ -1,10 +1,13 @@
module github.com/foomo/sesamy-cli
go 1.22.2
go 1.22.3
require (
github.com/fatih/structtag v1.2.0
github.com/gzuidhof/tygo v0.2.14
github.com/foomo/go v0.0.3
github.com/foomo/gocontemplate v0.1.4
github.com/foomo/sesamy-go v0.2.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.5.0
github.com/pkg/errors v0.9.1
github.com/pterm/pterm v0.12.79
@ -12,20 +15,22 @@ require (
github.com/spf13/viper v1.18.2
github.com/stoewer/go-strcase v1.3.0
github.com/stretchr/testify v1.9.0
golang.org/x/tools v0.21.0
google.golang.org/api v0.178.0
github.com/wissance/stringFormatter v1.2.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
google.golang.org/api v0.181.0
)
require (
atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
cloud.google.com/go/auth v0.3.0 // indirect
cloud.google.com/go/auth v0.4.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foomo/gostandards v0.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@ -57,10 +62,8 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
@ -68,7 +71,9 @@ require (
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

46
go.sum
View File

@ -7,8 +7,8 @@ atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtE
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs=
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
@ -42,6 +42,14 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foomo/go v0.0.3 h1:5pGzcPC78dImuBTT7nsZZnH+GIQUylbCtMkFEH26uZk=
github.com/foomo/go v0.0.3/go.mod h1:x6g64wiQusqaFElnh5rlk9unCgLKmfUWy0YFLejJxio=
github.com/foomo/gocontemplate v0.1.4 h1:MYSsoltno9pNMU5NwALd7PNSQ1XjN1tpqMGWAhCUETc=
github.com/foomo/gocontemplate v0.1.4/go.mod h1:BH8eODDwlqWhasSl86avbtMN3Zfgt4pWwr8/PBPM5v0=
github.com/foomo/gostandards v0.1.0 h1:dN6Yoj5un74W8hooC+boYcdbkTzF9jqU39q5kQCkzn4=
github.com/foomo/gostandards v0.1.0/go.mod h1:eyoFzndWb1kuDfupR/qf567mHeHZRi5//m64khreVac=
github.com/foomo/sesamy-go v0.2.0 h1:xGMYkxXda2P6GlMh6hBAXQgZZRw+ewfKPRPLyxv8Dfo=
github.com/foomo/sesamy-go v0.2.0/go.mod h1:Z3QdFwcADg+4epWxhfM+dEAV+Z0gxgb05ug/G6yNlZg=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@ -88,8 +96,6 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gzuidhof/tygo v0.2.14 h1:QRBD6eby2SMyYzv8KzXA+yopPbEO6w2Qzuuqqp9z+vU=
github.com/gzuidhof/tygo v0.2.14/go.mod h1:s3lpnppkDixQQhMWD78yPtAmugMHENsPWpQYziUIpw0=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -113,6 +119,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@ -135,8 +143,8 @@ github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IG
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@ -161,7 +169,6 @@ github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -173,6 +180,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/wissance/stringFormatter v1.2.0 h1:lB0zcJkTA1O4Eb2qSTJmyapla/LihQt6NpJLghwWSb0=
github.com/wissance/stringFormatter v1.2.0/go.mod h1:H7Mz15+5i8ypmv6bLknM/uD+U1teUW99PlW0DNCNscA=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
@ -187,18 +196,16 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -269,18 +276,17 @@ golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok=
google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U=
google.golang.org/api v0.181.0 h1:rPdjwnWgiPPOJx3IcSAQ2III5aX5tCer6wMpa/xmZi4=
google.golang.org/api v0.181.0/go.mod h1:MnQ+M0CFsfUwA5beZ+g/vCBCPXvtmZwRz2qzZk8ih1k=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU=
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=

View File

@ -1,82 +0,0 @@
package internal
import (
"fmt"
"go/ast"
"go/token"
"strings"
"github.com/fatih/structtag"
"github.com/foomo/sesamy-cli/pkg/config"
"github.com/pterm/pterm"
"github.com/stoewer/go-strcase"
"golang.org/x/tools/go/packages"
)
func GetEventParameters(source config.Tagmanager) (map[string][]string, error) {
ret := map[string][]string{}
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedSyntax | packages.NeedFiles,
}, source.PackageNames()...)
if err != nil {
return nil, err
}
for i, pkg := range pkgs {
if len(pkg.Errors) > 0 {
return nil, fmt.Errorf("%+v", pkg.Errors)
}
if len(pkg.GoFiles) == 0 {
return nil, fmt.Errorf("no input go files for package index %d", i)
}
conf := source.PackageConfig(pkg.ID)
for i, file := range pkg.Syntax {
if conf.IsFileIgnored(pkg.GoFiles[i]) {
continue
}
ast.Inspect(file, func(n ast.Node) bool {
// GenDecl can be an import, type, var, or const expression
if x, ok := n.(*ast.GenDecl); ok {
if x.Tok == token.IMPORT {
return false
}
for _, spec := range x.Specs {
// e.g. "type Foo struct {}" or "type Bar = string"
if elem, ok := spec.(*ast.TypeSpec); ok && elem.Name.IsExported() {
if strct, ok := elem.Type.(*ast.StructType); ok {
name := strcase.SnakeCase(elem.Name.String())
var fields []string
for _, field := range strct.Fields.List {
tags, err := structtag.Parse(strings.Trim(field.Tag.Value, "`"))
if err != nil {
pterm.Warning.Println(err.Error(), field.Tag.Value)
return false
}
tag, err := tags.Get("json")
if err != nil {
pterm.Warning.Println(err.Error())
return false
}
if tag.Value() != "" && tag.Value() != "-" {
fields = append(fields, strings.Split(tag.Value(), ",")[0])
}
}
ret[name] = fields
}
}
}
return false
}
return true
})
}
}
return ret, nil
}

1
pkg/code/doc.go Normal file
View File

@ -0,0 +1 @@
package code

78
pkg/code/file.go Normal file
View File

@ -0,0 +1,78 @@
package code
import (
"slices"
"strings"
"golang.org/x/exp/maps"
)
const (
SectionAnnotations = 0
SectionCopyright = 100
SectionHead = 200
SectionBody = 300
SectionFoot = 400
)
type File struct {
Sections map[int]*Section
}
func NewFile() *File {
return &File{
Sections: map[int]*Section{
SectionAnnotations: {},
SectionCopyright: {},
SectionHead: {},
SectionBody: {},
SectionFoot: {},
},
}
}
func (f *File) Annotations() *Section {
return f.Section(SectionAnnotations)
}
func (f *File) Copyright() *Section {
return f.Section(SectionCopyright)
}
func (f *File) Head() *Section {
return f.Section(SectionHead)
}
func (f *File) Body() *Section {
return f.Section(SectionBody)
}
func (f *File) Foot() *Section {
return f.Section(SectionFoot)
}
func (f *File) Section(id int) *Section {
return f.Sections[id]
}
func (f *File) AddSection(id int) {
f.Sections[id] = &Section{}
}
func (f *File) String() string {
b := &strings.Builder{}
sections := maps.Keys(f.Sections)
slices.Sort(sections)
for _, id := range sections {
section := f.Sections[id]
sectionParts := section.Parts
slices.Sort(sectionParts)
for _, part := range sectionParts {
b.WriteString(part + "\n")
}
}
return b.String()
}

35
pkg/code/section.go Normal file
View File

@ -0,0 +1,35 @@
package code
import (
"fmt"
"slices"
"github.com/wissance/stringFormatter"
)
type Section struct {
Parts []string
}
func (s *Section) Sprintf(format string, a ...any) {
value := fmt.Sprintf(format, a...)
if !slices.Contains(s.Parts, value) {
s.Parts = append(s.Parts, value)
}
}
// Tprintn {n} , n here is a number to notes order of argument list to use i.e. {0}, {1}
func (s *Section) Tprintn(template string, a ...any) {
value := stringFormatter.Format(template, a...)
if !slices.Contains(s.Parts, value) {
s.Parts = append(s.Parts, value)
}
}
// Tprintm {name} to notes arguments by name i.e. {name}, {last_name}, {address} and so on ...
func (s *Section) Tprintm(template string, a map[string]any) {
value := stringFormatter.FormatComplex(template, a)
if !slices.Contains(s.Parts, value) {
s.Parts = append(s.Parts, value)
}
}

15
pkg/config/package.go Normal file
View File

@ -0,0 +1,15 @@
package config
type Package struct {
Path string `yaml:"path"`
Events []string `yaml:"events"`
}
func (c Package) ExportEvent(event string) bool {
for _, name := range c.Events {
if name == event {
return true
}
}
return false
}

24
pkg/config/packages.go Normal file
View File

@ -0,0 +1,24 @@
package config
import (
"github.com/pkg/errors"
)
type Packages []Package
func (c Packages) PackageNames() []string {
ret := make([]string, len(c))
for i, value := range c {
ret[i] = value.Path
}
return ret
}
func (c Packages) PackageConfig(path string) (Package, error) {
for _, value := range c {
if value.Path == path {
return value, nil
}
}
return Package{}, errors.Errorf("package for path '%s' not found", path)
}

View File

@ -1,28 +1,15 @@
package config
import (
"github.com/gzuidhof/tygo/tygo"
"github.com/foomo/gocontemplate/pkg/contemplate"
)
type Tagmanager struct {
Packages []*tygo.PackageConfig `yaml:"packages"`
TypeMappings map[string]string `yaml:"type_mappings"`
Prefixes TagmanagerPrefixes `yaml:"prefixes"`
contemplate.Config `yaml:",squash"`
Tags TagmanagerTags `yaml:"tags"`
Prefixes TagmanagerPrefixes `yaml:"prefixes"`
}
func (e Tagmanager) PackageNames() []string {
ret := make([]string, len(e.Packages))
for i, value := range e.Packages {
ret[i] = value.Path
}
return ret
}
func (e Tagmanager) PackageConfig(path string) *tygo.PackageConfig {
for _, value := range e.Packages {
if value.Path == path {
return value
}
}
return nil
type TagmanagerTags struct {
GA4Enabled bool `yaml:"gA4_enabled"`
}

View File

@ -1,27 +1,10 @@
package config
import (
"github.com/gzuidhof/tygo/tygo"
"github.com/foomo/gocontemplate/pkg/contemplate"
)
type Typescript struct {
Packages []*tygo.PackageConfig `yaml:"packages"`
TypeMappings map[string]string `yaml:"type_mappings"`
}
func (e Typescript) PackageNames() []string {
ret := make([]string, len(e.Packages))
for i, value := range e.Packages {
ret[i] = value.Path
}
return ret
}
func (e Typescript) PackageConfig(path string) *tygo.PackageConfig {
for _, value := range e.Packages {
if value.Path == path {
return value
}
}
return nil
contemplate.Config `yaml:",squash"`
OutputPath string `yaml:"output_path"`
}

View File

@ -2,9 +2,11 @@ package tagmanager
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/mitchellh/hashstructure/v2"
"github.com/pkg/errors"
"google.golang.org/api/option"
"google.golang.org/api/tagmanager/v2"
@ -115,10 +117,6 @@ func (c *Client) WorkspaceID() string {
return c.workspaceID
}
func (c *Client) Notes() string {
return c.notes
}
func (c *Client) MeasurementID() string {
return c.measurementID
}
@ -146,323 +144,402 @@ func (c *Client) WorkspacePath() string {
return c.ContainerPath() + "/workspaces/" + c.workspaceID
}
func (c *Client) Notes(v any) string {
var hash string
if v != nil {
if value, err := hashstructure.Hash(v, hashstructure.FormatV2, nil); err != nil {
c.l.Warn("failed to hash struct:", "error", err)
} else {
hash = fmt.Sprintf(" [%d]", value)
}
}
return c.notes + hash
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Client) Client(name string) (*tagmanager.Client, error) {
if c.clients == nil {
c.clients = map[string]*tagmanager.Client{}
func (c *Client) GetClient(name string) (*tagmanager.Client, error) {
elems, err := c.LoadClients()
if err != nil {
return nil, err
}
c.l.Debug("listing", "type", "Client")
if _, ok := elems[name]; !ok {
return nil, ErrNotFound
}
return elems[name], nil
}
func (c *Client) LoadClients() (map[string]*tagmanager.Client, error) {
if c.clients == nil {
c.l.Debug("loading", "type", "Client")
r, err := c.Service().Accounts.Containers.Workspaces.Clients.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.Client{}
for _, value := range r.Client {
c.clients[value.Name] = value
res[value.Name] = value
}
c.clients = res
}
if _, ok := c.clients[name]; !ok {
return c.clients, nil
}
func (c *Client) GetFolder(name string) (*tagmanager.Folder, error) {
elems, err := c.LoadFolders()
if err != nil {
return nil, err
}
if _, ok := elems[name]; !ok {
return nil, ErrNotFound
}
return c.clients[name], nil
return elems[name], nil
}
func (c *Client) Folder(name string) (*tagmanager.Folder, error) {
func (c *Client) LoadFolders() (map[string]*tagmanager.Folder, error) {
if c.folders == nil {
c.folders = map[string]*tagmanager.Folder{}
c.l.Debug("listing", "type", "Folder")
c.l.Debug("loading", "type", "Folder")
r, err := c.Service().Accounts.Containers.Workspaces.Folders.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.Folder{}
for _, value := range r.Folder {
c.folders[value.Name] = value
res[value.Name] = value
}
c.folders = res
}
return c.folders, nil
}
func (c *Client) GetVariable(name string) (*tagmanager.Variable, error) {
elems, err := c.LoadVariables()
if err != nil {
return nil, err
}
if _, ok := c.folders[name]; !ok {
if _, ok := elems[name]; !ok {
return nil, ErrNotFound
}
return c.folders[name], nil
return elems[name], nil
}
func (c *Client) Variable(name string) (*tagmanager.Variable, error) {
func (c *Client) LoadVariables() (map[string]*tagmanager.Variable, error) {
if c.variables == nil {
c.variables = map[string]*tagmanager.Variable{}
c.l.Debug("listing", "type", "Variable")
c.l.Debug("loading", "type", "Variable")
r, err := c.Service().Accounts.Containers.Workspaces.Variables.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.Variable{}
for _, value := range r.Variable {
c.variables[value.Name] = value
res[value.Name] = value
}
c.variables = res
}
if _, ok := c.variables[name]; !ok {
return nil, ErrNotFound
}
return c.variables[name], nil
return c.variables, nil
}
func (c *Client) BuiltInVariable(name string) (*tagmanager.BuiltInVariable, error) {
if c.builtInVariables == nil {
c.builtInVariables = map[string]*tagmanager.BuiltInVariable{}
func (c *Client) GetBuiltInVariable(typeName string) (*tagmanager.BuiltInVariable, error) {
elems, err := c.LoadBuiltInVariables()
if err != nil {
return nil, err
}
c.l.Debug("listing", "type", "Built-In Variable")
if _, ok := elems[typeName]; !ok {
return nil, ErrNotFound
}
return elems[typeName], nil
}
func (c *Client) LoadBuiltInVariables() (map[string]*tagmanager.BuiltInVariable, error) {
if c.builtInVariables == nil {
c.l.Debug("loading", "type", "BuiltInVariable")
r, err := c.Service().Accounts.Containers.Workspaces.BuiltInVariables.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.BuiltInVariable{}
for _, value := range r.BuiltInVariable {
c.builtInVariables[value.Type] = value
res[value.Type] = value
}
c.builtInVariables = res
}
if _, ok := c.builtInVariables[name]; !ok {
return nil, ErrNotFound
}
return c.builtInVariables[name], nil
return c.builtInVariables, nil
}
func (c *Client) Trigger(name string) (*tagmanager.Trigger, error) {
if c.triggers == nil {
c.triggers = map[string]*tagmanager.Trigger{}
elems, err := c.LoadTriggers()
if err != nil {
return nil, err
}
c.l.Debug("listing", "type", "Trigger")
if _, ok := elems[name]; !ok {
return nil, ErrNotFound
}
return elems[name], nil
}
func (c *Client) LoadTriggers() (map[string]*tagmanager.Trigger, error) {
if c.triggers == nil {
c.l.Debug("loading", "type", "Trigger")
r, err := c.Service().Accounts.Containers.Workspaces.Triggers.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.Trigger{}
for _, value := range r.Trigger {
c.triggers[value.Name] = value
res[value.Name] = value
}
c.triggers = res
}
if _, ok := c.triggers[name]; !ok {
return nil, ErrNotFound
}
return c.triggers[name], nil
return c.triggers, nil
}
func (c *Client) Tag(name string) (*tagmanager.Tag, error) {
if c.tags == nil {
c.tags = map[string]*tagmanager.Tag{}
elems, err := c.LoadTags()
if err != nil {
return nil, err
}
c.l.Debug("listing", "type", "Tag")
if _, ok := elems[name]; !ok {
return nil, ErrNotFound
}
return elems[name], nil
}
func (c *Client) LoadTags() (map[string]*tagmanager.Tag, error) {
if c.tags == nil {
c.l.Debug("loading", "type", "Tag")
r, err := c.Service().Accounts.Containers.Workspaces.Tags.List(c.WorkspacePath()).Do()
if err != nil {
return nil, err
}
res := map[string]*tagmanager.Tag{}
for _, value := range r.Tag {
c.tags[value.Name] = value
res[value.Name] = value
}
c.tags = res
}
if _, ok := c.tags[name]; !ok {
return nil, ErrNotFound
}
return c.tags[name], nil
return c.tags, nil
}
func (c *Client) UpsertClient(client *tagmanager.Client) (*tagmanager.Client, error) {
l := c.l.With("type", "Client", "name", client.Name)
func (c *Client) UpsertClient(item *tagmanager.Client) (*tagmanager.Client, error) {
l := c.l.With("type", "Client", "name", item.Name)
client.AccountId = c.AccountID()
client.ContainerId = c.ContainerID()
client.WorkspaceId = c.WorkspaceID()
client.Notes = c.Notes()
item.Notes = c.Notes(item)
item.AccountId = c.AccountID()
item.ContainerId = c.ContainerID()
item.WorkspaceId = c.WorkspaceID()
cache, err := c.Client(client.Name)
cache, err := c.GetClient(item.Name)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
folder, err := c.Folder(c.folderName)
folder, err := c.GetFolder(c.folderName)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to retrieve folder")
}
client.ParentFolderId = folder.FolderId
item.ParentFolderId = folder.FolderId
if cache == nil {
l.Info("creating")
c.clients[client.Name], err = c.Service().Accounts.Containers.Workspaces.Clients.Create(c.WorkspacePath(), client).Do()
l.Info("⚠️ creating")
c.clients[item.Name], err = c.Service().Accounts.Containers.Workspaces.Clients.Create(c.WorkspacePath(), item).Do()
} else if item.Notes == cache.Notes {
l.Info("✅ unchanged", "id", cache.ClientId)
} else {
l.Info("updating", "id", cache.ClientId)
c.clients[client.Name], err = c.Service().Accounts.Containers.Workspaces.Clients.Update(c.WorkspacePath()+"/clients/"+cache.ClientId, client).Do()
l.Info("⚠️ updating", "id", cache.ClientId)
c.clients[item.Name], err = c.Service().Accounts.Containers.Workspaces.Clients.Update(c.WorkspacePath()+"/clients/"+cache.ClientId, item).Do()
}
if err != nil {
return nil, err
}
return c.Client(client.Name)
return c.GetClient(item.Name)
}
func (c *Client) UpsertFolder(name string) (*tagmanager.Folder, error) {
l := c.l.With("type", "Folder", "name", name)
cache, err := c.Folder(name)
item := &tagmanager.Folder{
Name: name,
}
item.Notes = c.Notes(item)
item.AccountId = c.AccountID()
item.ContainerId = c.ContainerID()
item.WorkspaceId = c.WorkspaceID()
cache, err := c.GetFolder(name)
if err != nil && !errors.Is(err, ErrNotFound) && !errors.Is(err, ErrNotFound) {
return nil, err
}
folder := &tagmanager.Folder{
AccountId: c.accountID,
ContainerId: c.containerID,
WorkspaceId: c.workspaceID,
Name: name,
Notes: c.notes,
}
if cache == nil {
l.Info("creating")
c.folders[name], err = c.Service().Accounts.Containers.Workspaces.Folders.Create(c.WorkspacePath(), folder).Do()
l.Info("⚠️ creating")
c.folders[name], err = c.Service().Accounts.Containers.Workspaces.Folders.Create(c.WorkspacePath(), item).Do()
} else if item.Notes == cache.Notes {
l.Info("✅ unchanged", "id", item.FolderId)
} else {
l.Info("updating", "id", cache.FolderId)
c.folders[name], err = c.Service().Accounts.Containers.Workspaces.Folders.Update(c.WorkspacePath()+"/folders/"+cache.FolderId, folder).Do()
l.Info("⚠️ updating", "id", cache.FolderId)
c.folders[name], err = c.Service().Accounts.Containers.Workspaces.Folders.Update(c.WorkspacePath()+"/folders/"+cache.FolderId, item).Do()
}
if err != nil {
return nil, err
}
return c.Folder(name)
return c.GetFolder(name)
}
func (c *Client) UpsertVariable(variable *tagmanager.Variable) (*tagmanager.Variable, error) {
l := c.l.With("type", "Variable", "name", variable.Name)
func (c *Client) UpsertVariable(item *tagmanager.Variable) (*tagmanager.Variable, error) {
l := c.l.With("type", "Variable", "name", item.Name)
variable.AccountId = c.AccountID()
variable.ContainerId = c.ContainerID()
variable.WorkspaceId = c.WorkspaceID()
variable.Notes = c.Notes()
item.Notes = c.Notes(item)
item.AccountId = c.AccountID()
item.ContainerId = c.ContainerID()
item.WorkspaceId = c.WorkspaceID()
cache, err := c.Variable(variable.Name)
cache, err := c.GetVariable(item.Name)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
folder, err := c.Folder(c.folderName)
folder, err := c.GetFolder(c.folderName)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to retrieve folder")
}
variable.ParentFolderId = folder.FolderId
item.ParentFolderId = folder.FolderId
if cache == nil {
l.Info("creating")
c.variables[variable.Name], err = c.Service().Accounts.Containers.Workspaces.Variables.Create(c.WorkspacePath(), variable).Do()
l.Info("⚠️ creating")
c.variables[item.Name], err = c.Service().Accounts.Containers.Workspaces.Variables.Create(c.WorkspacePath(), item).Do()
} else if item.Notes == cache.Notes {
l.Info("✅ unchanged", "id", cache.VariableId)
} else {
l.Info("updating", "id", cache.VariableId)
c.variables[variable.Name], err = c.Service().Accounts.Containers.Workspaces.Variables.Update(c.WorkspacePath()+"/variables/"+cache.VariableId, variable).Do()
l.Info("⚠️ updating", "id", cache.VariableId)
c.variables[item.Name], err = c.Service().Accounts.Containers.Workspaces.Variables.Update(c.WorkspacePath()+"/variables/"+cache.VariableId, item).Do()
}
if err != nil {
return nil, err
}
return c.Variable(variable.Name)
return c.GetVariable(item.Name)
}
func (c *Client) EnableBuiltInVariable(name string) (*tagmanager.BuiltInVariable, error) {
l := c.l.With("type", "Built-In Variable", "name", name)
func (c *Client) EnableBuiltInVariable(typeName string) (*tagmanager.BuiltInVariable, error) {
l := c.l.With("type", "Built-In Variable", "typeName", typeName)
cache, err := c.BuiltInVariable(name)
cache, err := c.GetBuiltInVariable(typeName)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if cache != nil {
l.Info("✅ unchanged")
return cache, nil
}
l.Info("creating")
resp, err := c.Service().Accounts.Containers.Workspaces.BuiltInVariables.Create(c.WorkspacePath()).Type(name).Do()
l.Info("⚠️ creating")
resp, err := c.Service().Accounts.Containers.Workspaces.BuiltInVariables.Create(c.WorkspacePath()).Type(typeName).Do()
if err != nil {
return nil, errors.Wrap(err, "failed to create built-in variable")
}
for _, builtInVariable := range resp.BuiltInVariable {
l.Debug(builtInVariable.Type)
c.builtInVariables[builtInVariable.Type] = builtInVariable
}
return c.BuiltInVariable(name)
return c.GetBuiltInVariable(typeName)
}
func (c *Client) UpsertTrigger(trigger *tagmanager.Trigger) (*tagmanager.Trigger, error) {
l := c.l.With("type", "Trigger", "name", trigger.Name)
func (c *Client) UpsertTrigger(item *tagmanager.Trigger) (*tagmanager.Trigger, error) {
l := c.l.With("type", "Trigger", "name", item.Name)
trigger.AccountId = c.AccountID()
trigger.ContainerId = c.ContainerID()
trigger.WorkspaceId = c.WorkspaceID()
trigger.Notes = c.Notes()
item.Notes = c.Notes(item)
item.AccountId = c.AccountID()
item.ContainerId = c.ContainerID()
item.WorkspaceId = c.WorkspaceID()
cache, err := c.Trigger(trigger.Name)
cache, err := c.Trigger(item.Name)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
folder, err := c.Folder(c.folderName)
folder, err := c.GetFolder(c.folderName)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to retrieve folder")
}
trigger.ParentFolderId = folder.FolderId
item.ParentFolderId = folder.FolderId
if cache == nil {
l.Info("creating")
c.triggers[trigger.Name], err = c.Service().Accounts.Containers.Workspaces.Triggers.Create(c.WorkspacePath(), trigger).Do()
l.Info("⚠️ creating")
c.triggers[item.Name], err = c.Service().Accounts.Containers.Workspaces.Triggers.Create(c.WorkspacePath(), item).Do()
} else if item.Notes == cache.Notes {
l.Info("✅ unchanged", "id", cache.TriggerId)
} else {
l.Info("updating", "id", cache.TriggerId)
c.triggers[trigger.Name], err = c.Service().Accounts.Containers.Workspaces.Triggers.Update(c.WorkspacePath()+"/triggers/"+cache.TriggerId, trigger).Do()
l.Info("⚠️ updating", "id", cache.TriggerId)
c.triggers[item.Name], err = c.Service().Accounts.Containers.Workspaces.Triggers.Update(c.WorkspacePath()+"/triggers/"+cache.TriggerId, item).Do()
}
if err != nil {
return nil, err
}
return c.Trigger(trigger.Name)
return c.Trigger(item.Name)
}
func (c *Client) UpsertTag(tag *tagmanager.Tag) (*tagmanager.Tag, error) {
l := c.l.With("type", "Tag", "name", tag.Name)
func (c *Client) UpsertTag(item *tagmanager.Tag) (*tagmanager.Tag, error) {
l := c.l.With("type", "Tag", "name", item.Name)
tag.AccountId = c.AccountID()
tag.ContainerId = c.ContainerID()
tag.WorkspaceId = c.WorkspaceID()
tag.Notes = c.Notes()
item.Notes = c.Notes(item)
item.AccountId = c.AccountID()
item.ContainerId = c.ContainerID()
item.WorkspaceId = c.WorkspaceID()
cache, err := c.Tag(tag.Name)
cache, err := c.Tag(item.Name)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
folder, err := c.Folder(c.folderName)
folder, err := c.GetFolder(c.folderName)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to retrieve folder")
}
tag.ParentFolderId = folder.FolderId
item.ParentFolderId = folder.FolderId
if cache == nil {
l.Info("creating")
c.tags[tag.Name], err = c.Service().Accounts.Containers.Workspaces.Tags.Create(c.WorkspacePath(), tag).Do()
l.Info("⚠️ creating")
c.tags[item.Name], err = c.Service().Accounts.Containers.Workspaces.Tags.Create(c.WorkspacePath(), item).Do()
} else if item.Notes == cache.Notes {
l.Info("✅ unchanged", "id", cache.TagId)
} else {
l.Info("updating", "id", cache.TagId)
c.tags[tag.Name], err = c.Service().Accounts.Containers.Workspaces.Tags.Update(c.WorkspacePath()+"/tags/"+cache.TagId, tag).Do()
l.Info("⚠️ updating", "id", cache.TagId)
c.tags[item.Name], err = c.Service().Accounts.Containers.Workspaces.Tags.Update(c.WorkspacePath()+"/tags/"+cache.TagId, item).Do()
}
if err != nil {
return nil, err
}
return c.Tag(tag.Name)
return c.Tag(item.Name)
}

View File

@ -0,0 +1,19 @@
package client
import (
"google.golang.org/api/tagmanager/v2"
)
func NewMPv2(name string) *tagmanager.Client {
return &tagmanager.Client{
Name: name,
Parameter: []*tagmanager.Parameter{
{
Type: "template",
Key: "activationPath",
Value: "/mp/collect",
},
},
Type: "mpaw_client",
}
}

View File

@ -4,9 +4,14 @@ import (
"google.golang.org/api/tagmanager/v2"
)
func NewServerGA4Event(name string, measurementID *tagmanager.Variable, trigger *tagmanager.Trigger) *tagmanager.Tag {
func NewServerGA4Event(name string, measurementID *tagmanager.Variable, triggers ...*tagmanager.Trigger) *tagmanager.Tag {
triggerIDs := make([]string, len(triggers))
for i, trigger := range triggers {
triggerIDs[i] = trigger.TriggerId
}
return &tagmanager.Tag{
FiringTriggerId: []string{trigger.TriggerId},
FiringTriggerId: triggerIDs,
Name: name,
Parameter: []*tagmanager.Parameter{
{

View File

@ -0,0 +1,42 @@
package generator
import (
"log/slog"
"strings"
"github.com/foomo/gocontemplate/pkg/contemplate"
"github.com/foomo/sesamy-cli/pkg/code"
)
type (
Options struct {
PackageNameReplacer *strings.Replacer
}
Option func(*Options)
)
func Generate(l *slog.Logger, ctpl *contemplate.Contemplate, opts ...Option) (map[string]*code.File, error) {
l.Info("👷 generating typescript code")
o := Options{
PackageNameReplacer: strings.NewReplacer(".", "_", "/", "_", "-", "_"),
}
for _, opt := range opts {
if opt != nil {
opt(&o)
}
}
ret := make(map[string]*code.File, len(ctpl.Packages))
for path, pkg := range ctpl.Packages {
l.Debug("👷 adding package", "name", pkg.Name(), "path", pkg.Path())
inst := NewPackage(l, ctpl, pkg, o.PackageNameReplacer)
if err := inst.Generate(); err != nil {
return nil, err
}
if len(inst.Code().Body().Parts) > 0 {
ret[o.PackageNameReplacer.Replace(path)+".ts"] = inst.Code()
}
}
return ret, nil
}

View File

@ -0,0 +1,365 @@
package generator_test
import (
"io"
"log/slog"
"testing"
"github.com/foomo/gocontemplate/pkg/contemplate"
"github.com/foomo/sesamy-cli/pkg/typescript/generator"
_ "github.com/foomo/sesamy-go/pkg/event/params" // force inclusion
_ "github.com/foomo/sesamy-go/pkg/sesamy" // force inclusion
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
expectedEvent = `
// Code generated by sesamy. DO NOT EDIT.
import type * as github_com_foomo_sesamy_go_pkg_event_params from './github_com_foomo_sesamy_go_pkg_event_params';
import { EventName } from './github_com_foomo_sesamy_go_pkg_sesamy';
import { collect } from '@foomo/sesamy';
export const addToCart = (
params: github_com_foomo_sesamy_go_pkg_event_params.AddToCart<github_com_foomo_sesamy_go_pkg_event_params.Item>,
) => {
collect({
name: EventName.AddToCart,
params,
});
}
export const pageView = (
params: github_com_foomo_sesamy_go_pkg_event_params.PageView,
) => {
collect({
name: EventName.PageView,
params,
});
}
`
expectedParams = `
// Code generated by sesamy. DO NOT EDIT.
import type * as github_com_foomo_gostandards_iso4217 from './github_com_foomo_gostandards_iso4217';
export interface AddToCart<I> {
currency?: github_com_foomo_gostandards_iso4217.Currency;
value?: number;
items?: Array<I>;
}
export interface Item {
affiliation?: string;
coupon?: string;
creative_name?: string;
creative_slot?: string;
discount?: number;
index?: number;
item_brand?: string;
item_category?: string;
item_category2?: string;
item_category3?: string;
item_category4?: string;
item_category5?: string;
item_id?: string;
item_list_id?: string;
item_list_name?: string;
item_name?: string;
item_variant?: string;
location_id?: string;
price?: number;
promotion_id?: string;
promotion_name?: string;
quantity?: number;
}
export interface PageView {
page_title?: string;
page_location?: string;
}
`
expectedSesamy = `
// Code generated by sesamy. DO NOT EDIT.
export enum EventName {
AdImpression = "ad_impression",
AddPaymentInfo = "add_payment_info",
AddShippingInfo = "add_shipping_info",
AddToCart = "add_to_cart",
AddToWishlist = "add_to_wishlist",
BeginCheckout = "begin_checkout",
CampaignDetails = "campaign_details",
Click = "click",
EarnVirtualMoney = "earn_virtual_money",
FileDownload = "file_download",
FormStart = "form_start",
FormSubmit = "form_submit",
GenerateLead = "generate_lead",
JoinGroup = "join_group",
LevelEnd = "level_end",
LevelStart = "level_start",
LevelUp = "level_up",
Login = "login",
PageView = "page_view",
PostScore = "post_score",
Purchase = "purchase",
Refund = "refund",
RemoveFromCart = "remove_from_cart",
ScreenView = "screen_view",
Scroll = "scroll",
Search = "search",
SelectContent = "select_content",
SelectItem = "select_item",
SelectPromotion = "select_promotion",
SessionStart = "session_start",
Share = "share",
SignUp = "sign_up",
SpendVirtualCurrency = "spend_virtual_currency",
TutorialBegin = "tutorial_begin",
TutorialComplete = "tutorial_complete",
UnlockAchievement = "unlock_achievement",
UserEngagement = "user_engagement",
VideoComplete = "video_complete",
VideoProgress = "video_progress",
VideoStart = "video_start",
ViewCart = "view_cart",
ViewItem = "view_item",
ViewItemList = "view_item_list",
ViewPromotion = "view_promotion",
ViewSearchResults = "view_search_results",
}
export interface Event<P> {
name: EventName;
params?: P;
}
`
expectedISO4217 = `
// Code generated by sesamy. DO NOT EDIT.
export enum Currency {
AED = "AED",
AFN = "AFN",
ALL = "ALL",
AMD = "AMD",
ANG = "ANG",
AOA = "AOA",
ARS = "ARS",
AUD = "AUD",
AWG = "AWG",
AZN = "AZN",
BAM = "BAM",
BBD = "BBD",
BDT = "BDT",
BGN = "BGN",
BHD = "BHD",
BIF = "BIF",
BMD = "BMD",
BND = "BND",
BOB = "BOB",
BOV = "BOV",
BRL = "BRL",
BSD = "BSD",
BTN = "BTN",
BWP = "BWP",
BYN = "BYN",
BZD = "BZD",
CAD = "CAD",
CDF = "CDF",
CHE = "CHE",
CHF = "CHF",
CHW = "CHW",
CLF = "CLF",
CLP = "CLP",
CNY = "CNY",
COP = "COP",
COU = "COU",
CRC = "CRC",
CUP = "CUP",
CVE = "CVE",
CZK = "CZK",
DJF = "DJF",
DKK = "DKK",
DOP = "DOP",
DZD = "DZD",
EGP = "EGP",
ERN = "ERN",
ETB = "ETB",
EUR = "EUR",
FJD = "FJD",
FKP = "FKP",
GBP = "GBP",
GEL = "GEL",
GHS = "GHS",
GIP = "GIP",
GMD = "GMD",
GNF = "GNF",
GTQ = "GTQ",
GYD = "GYD",
HKD = "HKD",
HNL = "HNL",
HTG = "HTG",
HUF = "HUF",
IDR = "IDR",
ILS = "ILS",
INR = "INR",
IQD = "IQD",
IRR = "IRR",
ISK = "ISK",
JMD = "JMD",
JOD = "JOD",
JPY = "JPY",
KES = "KES",
KGS = "KGS",
KHR = "KHR",
KMF = "KMF",
KPW = "KPW",
KRW = "KRW",
KWD = "KWD",
KYD = "KYD",
KZT = "KZT",
LAK = "LAK",
LBP = "LBP",
LKR = "LKR",
LRD = "LRD",
LSL = "LSL",
LYD = "LYD",
MAD = "MAD",
MDL = "MDL",
MGA = "MGA",
MKD = "MKD",
MMK = "MMK",
MNT = "MNT",
MOP = "MOP",
MRU = "MRU",
MUR = "MUR",
MVR = "MVR",
MWK = "MWK",
MXN = "MXN",
MXV = "MXV",
MYR = "MYR",
MZN = "MZN",
NAD = "NAD",
NGN = "NGN",
NIO = "NIO",
NOK = "NOK",
NPR = "NPR",
NZD = "NZD",
OMR = "OMR",
PAB = "PAB",
PEN = "PEN",
PGK = "PGK",
PHP = "PHP",
PKR = "PKR",
PLN = "PLN",
PYG = "PYG",
QAR = "QAR",
RON = "RON",
RSD = "RSD",
RUB = "RUB",
RWF = "RWF",
SAR = "SAR",
SBD = "SBD",
SCR = "SCR",
SDG = "SDG",
SEK = "SEK",
SGD = "SGD",
SHP = "SHP",
SLE = "SLE",
SLL = "SLL",
SOS = "SOS",
SRD = "SRD",
SSP = "SSP",
STN = "STN",
SVC = "SVC",
SYP = "SYP",
SZL = "SZL",
THB = "THB",
TJS = "TJS",
TMT = "TMT",
TND = "TND",
TOP = "TOP",
TRY = "TRY",
TTD = "TTD",
TWD = "TWD",
TZS = "TZS",
UAH = "UAH",
UGX = "UGX",
USD = "USD",
USN = "USN",
UYI = "UYI",
UYU = "UYU",
UYW = "UYW",
UZS = "UZS",
VED = "VED",
VES = "VES",
VND = "VND",
VUV = "VUV",
WST = "WST",
XAF = "XAF",
XAG = "XAG",
XAU = "XAU",
XBA = "XBA",
XBB = "XBB",
XBC = "XBC",
XBD = "XBD",
XCD = "XCD",
XDR = "XDR",
XOF = "XOF",
XPD = "XPD",
XPF = "XPF",
XPT = "XPT",
XSU = "XSU",
XTS = "XTS",
XUA = "XUA",
XXX = "XXX",
YER = "YER",
ZAR = "ZAR",
ZMW = "ZMW",
ZWL = "ZWL",
}
`
)
func TestGenerate(t *testing.T) {
cfg := &contemplate.Config{
Packages: []*contemplate.PackageConfig{
{
Path: "github.com/foomo/sesamy-go/pkg/event",
Types: []string{"AddToCart", "PageView"},
},
},
}
ctpl, err := contemplate.Load(cfg)
require.NoError(t, err)
l := slog.New(slog.NewTextHandler(io.Discard, nil))
actual, err := generator.Generate(l, ctpl)
require.NoError(t, err)
require.Len(t, actual, 4)
t.Run("event.ts", func(t *testing.T) {
if !assert.Equal(t, expectedEvent, "\n"+actual["github_com_foomo_sesamy_go_pkg_event.ts"].String()) {
t.Log("\n" + actual["github_com_foomo_sesamy_go_pkg_event.ts"].String())
}
})
t.Run("sesamy.ts", func(t *testing.T) {
if !assert.Equal(t, expectedSesamy, "\n"+actual["github_com_foomo_sesamy_go_pkg_sesamy.ts"].String()) {
t.Log("\n" + actual["github_com_foomo_sesamy_go_pkg_sesamy.ts"].String())
}
})
t.Run("params.ts", func(t *testing.T) {
if !assert.Equal(t, expectedParams, "\n"+actual["github_com_foomo_sesamy_go_pkg_event_params.ts"].String()) {
t.Log("\n" + actual["github_com_foomo_sesamy_go_pkg_event_params.ts"].String())
}
})
t.Run("iso4217.ts", func(t *testing.T) {
if !assert.Equal(t, expectedISO4217, "\n"+actual["github_com_foomo_gostandards_iso4217.ts"].String()) {
t.Log("\n" + actual["github_com_foomo_gostandards_iso4217.ts"].String())
}
})
}

View File

@ -0,0 +1,348 @@
package generator
import (
"fmt"
"go/ast"
"go/types"
"log/slog"
"slices"
"strings"
"github.com/fatih/structtag"
"github.com/foomo/gocontemplate/pkg/assume"
"github.com/foomo/gocontemplate/pkg/contemplate"
"github.com/foomo/sesamy-cli/pkg/code"
"github.com/foomo/sesamy-cli/pkg/typescript"
"github.com/stoewer/go-strcase"
"golang.org/x/exp/maps"
)
const FallbackType = "any"
type Package struct {
l *slog.Logger
Filename string
goctpl *contemplate.Contemplate
pkg *contemplate.Package
code *code.File
imports *typescript.Imports
pkgNameReplacer *strings.Replacer
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewPackage(l *slog.Logger, goctpl *contemplate.Contemplate, pkg *contemplate.Package, pkgNameReplacer *strings.Replacer) *Package {
inst := &Package{
l: l,
goctpl: goctpl,
pkg: pkg,
Filename: pkgNameReplacer.Replace(pkg.Path()) + ".ts",
pkgNameReplacer: pkgNameReplacer,
code: code.NewFile(),
imports: typescript.NewImports(),
}
return inst
}
// ------------------------------------------------------------------------------------------------
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (p *Package) Code() *code.File {
return p.code
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (p *Package) IsSesamyEvent(expr ast.Expr) bool {
if ident := assume.T[*ast.Ident](expr); ident != nil {
if typeSpec := assume.T[*ast.TypeSpec](ident.Obj.Decl); typeSpec != nil {
if indexExpr := assume.T[*ast.IndexExpr](typeSpec.Type); indexExpr != nil {
if selectorExpr := assume.T[*ast.SelectorExpr](indexExpr.X); selectorExpr != nil {
if xIdent := assume.T[*ast.Ident](selectorExpr.X); xIdent != nil {
return xIdent.Name == "sesamy" && selectorExpr.Sel.Name == "Event"
}
}
}
}
}
return false
}
func (p *Package) Generate() error {
// add annotations
p.code.Annotations().Sprintf(`// Code generated by sesamy. DO NOT EDIT.`)
var err error
for scopeTypeName, scopeType := range p.pkg.ScopeTypes() {
switch t := scopeType.Type().Underlying().(type) {
case *types.Struct:
expr := p.pkg.LookupExpr(scopeTypeName)
if p.IsSesamyEvent(expr) {
err = p.renderSesamyEvent(expr)
} else {
err = p.renderStruct(scopeType, scopeTypeName, t)
}
if err != nil {
return err
}
case *types.Basic:
p.renderTypeBasic(scopeTypeName, scopeType)
default:
p.l.Debug("👷 unhandled type", "type", t)
}
}
p.imports.Write(p.code.Head())
return nil
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (p *Package) renderTypeBasic(typeName string, typeObject types.Object) {
res := map[string]string{}
for _, object := range p.goctpl.LookupTypesByType(typeObject) {
if objectType := assume.T[*types.Const](object); objectType != nil {
if objectTypeNamed := assume.T[*types.Named](objectType.Type()); objectTypeNamed != nil {
if objectTypeNamed.Obj() == typeObject {
res[strings.TrimPrefix(object.Name(), typeName)] = objectType.Val().String()
}
}
}
}
names := maps.Keys(res)
slices.Sort(names)
typeValues := &strings.Builder{}
for _, name := range names {
typeValues.WriteString(fmt.Sprintf("\t%s = %s,\n", name, res[name]))
}
p.code.Body().Tprintn(`
export enum {0} {
{1}
}`,
typeName,
strings.TrimSuffix(typeValues.String(), "\n"),
)
}
func (p *Package) tsASTExpr(input ast.Expr) string {
switch t := input.(type) {
case *ast.SelectorExpr:
return p.tsAstType(p.pkg.Raw().TypesInfo.TypeOf(t.Sel))
case *ast.IndexExpr:
return fmt.Sprintf("%s<%s>", p.tsASTExpr(t.X), p.tsASTExpr(t.Index))
case *ast.Ident:
return t.Name
default:
p.l.Debug("👷 unhandled type", "type", t)
return FallbackType
}
}
func (p *Package) tsAstType(input types.Type) string {
switch t := input.(type) {
case *types.Named:
return p.tsTypeFromNamed(t)
default:
p.l.Debug("👷 unhandled type", "type", t)
return FallbackType
}
}
func (p *Package) renderSesamyEvent(v ast.Expr) error {
p.imports.Import("@foomo/sesamy").AddModule("collect")
p.imports.Import("./github_com_foomo_sesamy_go_pkg_sesamy").AddModule("EventName")
var typeName string
var typeType string
if inputIdent := assume.T[*ast.Ident](v); inputIdent != nil {
typeName = inputIdent.Name
if inputTypeSpec := assume.T[*ast.TypeSpec](inputIdent.Obj.Decl); inputTypeSpec != nil {
if inputIndexExpr := assume.T[*ast.IndexExpr](inputTypeSpec.Type); inputIndexExpr != nil {
typeType = p.tsASTExpr(inputIndexExpr.Index)
}
}
}
p.code.Body().Tprintn(`
export const {0} = (
params: {1},
) => {
collect({
name: EventName.{2},
params,
});
}`,
strcase.LowerCamelCase(typeName),
typeType,
typeName,
)
return nil
}
func (p *Package) renderStruct(obj types.Object, typeName string, t *types.Struct) error {
typeFields := &strings.Builder{}
for i := range t.NumFields() {
tag := t.Tag(i)
field := t.Field(i)
name := field.Name()
tags, err := structtag.Parse(strings.Trim(tag, "`"))
if err != nil {
return err
}
tagVal, err := tags.Get("json")
if err != nil {
return err
}
if value := tagVal.Value(); value != "" && value != "-" {
name = tagVal.Name
}
if tagVal.HasOption("omitempty") {
name += "?"
}
typeFields.WriteString(
fmt.Sprintf("\t%s: %s;\n", name, p.tsType(t.Field(i).Type())),
)
}
if typeFields.Len() == 0 {
p.code.Body().Tprintn(`
export type {0} = {1}`,
p.tsDecl(obj.Type()),
"{}",
)
} else {
p.code.Body().Tprintn(`
export interface {0} {
{1}
}`,
p.tsDecl(obj.Type()),
strings.TrimSuffix(typeFields.String(), "\n"),
)
}
return nil
}
func (p *Package) tsDecl(v types.Type) string {
switch t := v.(type) {
case *types.Named:
return p.tsDeclFromNamed(t)
default:
return ""
}
}
func (p *Package) tsDeclFromNamed(v *types.Named) string {
return p.tsDeclFromTypeName(v.Obj()) + p.tsDeclFromTypeParamList(v.TypeParams())
}
func (p *Package) tsDeclFromTypeName(v *types.TypeName) string {
return v.Name()
}
func (p *Package) tsDeclFromTypeParamList(v *types.TypeParamList) string {
if v == nil {
return ""
}
params := make([]string, v.Len())
for i := range v.Len() {
params[i] = p.tsDeclFromTypeParam(v.At(i))
}
if len(params) > 0 {
return "<" + strings.Join(params, ", ") + ">"
}
return ""
}
func (p *Package) tsDeclFromTypeParam(v *types.TypeParam) string {
return v.Obj().Name()
}
func (p *Package) tsType(v types.Type) string {
switch t := v.(type) {
case *types.Pointer:
return p.tsType(t.Elem()) + " | undefined"
case *types.Basic:
return p.tsTypeFromBasicInfoType(t.Info())
case *types.Named:
return p.tsTypeFromNamed(t)
case *types.TypeParam:
return p.tsTypeFromTypeName(t.Obj())
case *types.Slice:
return "Array<" + p.tsType(t.Elem()) + ">"
default:
p.l.Debug("👷 unhandled type", "type", t)
return FallbackType
}
}
func (p *Package) tsTypeFromBasicInfoType(c types.BasicInfo) string {
switch {
case c&types.IsNumeric != 0:
return "number"
case c&types.IsString != 0:
return "string"
case c&types.IsBoolean != 0:
return "boolean"
default:
p.l.Debug("👷 unhandled type", "type", c)
return FallbackType
}
}
func (p *Package) tsTypeFromTypeName(t *types.TypeName) string {
name := t.Name()
// add package prefix is needed
if t.Pkg().Path() != p.pkg.Path() {
// retrieve ts package name
tsPkg := p.pkgNameReplacer.Replace(t.Pkg().Path())
// ensure import exists
p.imports.Import("./" + tsPkg).SetDefault("type * as " + tsPkg)
name = tsPkg + "." + name
}
return name
}
func (p *Package) tsTypeFromNamed(t *types.Named) string {
return p.tsTypeFromTypeName(t.Obj()) // + b.tsTypeFromTypeParamList(t.TypeParams())
}
// func (p *Package) tsTypeFromTypeParamList(t *types.TypeParamList) string {
// if t == nil {
// return ""
// }
// params := make([]string, t.Len())
// for i := range t.Len() {
// params[i] = p.tsTypeFromTypeParam(t.At(i))
// }
//
// if len(params) > 0 {
// return "<" + strings.Join(params, ", ") + ">"
// }
//
// return ""
// }
// func (p *Package) tsTypeFromTypeParam(t *types.TypeParam) string {
// return p.tsType(t)
// }

110
pkg/typescript/import.go Normal file
View File

@ -0,0 +1,110 @@
package typescript
import (
"fmt"
"slices"
"strings"
"golang.org/x/exp/maps"
)
type Import struct {
def string
location string
// types indicates if the given module is a type
types map[string]bool
// modules map of map[name]alias
modules map[string]string
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewImport(location string) *Import {
return &Import{
location: location,
types: make(map[string]bool),
modules: make(map[string]string),
}
}
// ------------------------------------------------------------------------------------------------
// ~ Getter
// ------------------------------------------------------------------------------------------------
func (i *Import) Default() string {
return i.def
}
func (i *Import) Location() string {
return i.def
}
func (i *Import) SetDefault(v string) {
i.def = v
}
func (i *Import) Modules() []string {
ret := maps.Keys(i.modules)
slices.Sort(ret)
return ret
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (i *Import) IsType(name string) bool {
v, ok := i.types[name]
return ok && v
}
func (i *Import) Alias(name string) string {
if v, ok := i.modules[name]; ok {
return v
}
return ""
}
func (i *Import) AddModule(name string) {
i.modules[name] = ""
}
func (i *Import) AddModuleWithAlias(name, alias string) {
i.modules[name] = alias
}
func (i *Import) AddTypeModule(name string) {
i.types[name] = true
i.modules[name] = ""
}
func (i *Import) AddTypeModuleWithAlias(name, alias string) {
i.types[name] = true
i.modules[name] = alias
}
func (i *Import) String() string {
var defModules []string
if i.def != "" {
defModules = append(defModules, i.def)
}
if len(i.modules) > 0 {
var modules []string
for name, alias := range i.modules {
if alias != "" {
name += " as " + alias
}
if i.IsType(name) {
name = "type " + name
}
modules = append(modules, name)
}
defModules = append(defModules, "{ "+strings.Join(modules, ", ")+" }")
}
return fmt.Sprintf("import %s from '%s';", strings.Join(defModules, ", "), i.location)
}

36
pkg/typescript/imports.go Normal file
View File

@ -0,0 +1,36 @@
package typescript
import (
"github.com/foomo/sesamy-cli/pkg/code"
)
type Imports struct {
imports map[string]*Import
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewImports() *Imports {
return &Imports{
imports: map[string]*Import{},
}
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (i *Imports) Import(location string) *Import {
if _, ok := i.imports[location]; !ok {
i.imports[location] = NewImport(location)
}
return i.imports[location]
}
func (i *Imports) Write(f *code.Section) {
for key := range i.imports {
f.Sprintf(i.imports[key].String())
}
}

43
pkg/typescript/tag.go Normal file
View File

@ -0,0 +1,43 @@
package typescript
import (
"strings"
"github.com/fatih/structtag"
)
func ParseTagName(value string) (string, error) {
tags, err := structtag.Parse(strings.Trim(value, "`"))
if err != nil {
return "", err
}
tag, err := tags.Get("json")
if err != nil {
return "", err
}
if tag.Value() != "" && tag.Value() != "-" {
return strings.Split(tag.Value(), ",")[0], nil
}
return "", nil
}
func ParseTagOmitempty(value string) (string, error) {
tags, err := structtag.Parse(strings.Trim(value, "`"))
if err != nil {
return "", err
}
tag, err := tags.Get("json")
if err != nil {
return "", err
}
if tag.Value() != "" && tag.Value() != "-" {
return strings.Split(tag.Value(), ",")[0], nil
}
return "", nil
}

View File

@ -30,9 +30,9 @@ typescript:
tagmanager:
packages:
- path: 'github.com/foomo/sesamy-cli/_example/server'
output_path: './_example/client/types.d.ts'
exclude_files:
- item.go
events:
- PageView
- SelectItem
prefixes:
client: ''
folder: ''

View File

@ -8,6 +8,8 @@ import (
"os"
"testing"
testingx "github.com/foomo/go/testing"
tagx "github.com/foomo/go/testing/tag"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/foomo/sesamy-cli/pkg/tagmanager/trigger"
"github.com/foomo/sesamy-cli/pkg/tagmanager/variable"
@ -18,7 +20,7 @@ import (
)
func TestNewClient_Server(t *testing.T) {
t.Skip()
testingx.Tags(t, tagx.Skip)
c, err := tagmanager.NewClient(
context.TODO(),
@ -101,7 +103,7 @@ func TestNewClient_Server(t *testing.T) {
}
func TestNewClient_Web(t *testing.T) {
t.Skip()
testingx.Tags(t, tagx.Skip)
c, err := tagmanager.NewClient(
context.TODO(),
@ -150,7 +152,7 @@ func TestNewClient_Web(t *testing.T) {
ContainerId: c.ContainerID(),
WorkspaceId: c.WorkspaceID(),
Name: name,
Notes: c.Notes(),
Notes: c.Notes(nil),
})
if r, err := cmd.Do(); assert.NoError(t, err) {
dump(t, r)
@ -211,7 +213,7 @@ func TestNewClient_Web(t *testing.T) {
WorkspaceId: c.WorkspaceID(),
ParentFolderId: folderID,
Name: "Constant." + name,
Notes: c.Notes(),
Notes: c.Notes(nil),
Parameter: []*gtagmanager.Parameter{
{
Key: "value",
@ -263,7 +265,7 @@ func TestNewClient_Web(t *testing.T) {
ParentFolderId: folderID,
Type: "customEvent",
Name: "Event." + name,
Notes: c.Notes(),
Notes: c.Notes(nil),
CustomEventFilter: []*gtagmanager.Condition{
{
Parameter: []*gtagmanager.Parameter{
@ -323,7 +325,7 @@ func TestNewClient_Web(t *testing.T) {
WorkspaceId: c.WorkspaceID(),
ParentFolderId: folderID,
Name: "GA4." + name,
Notes: c.Notes(),
Notes: c.Notes(nil),
Parameter: []*gtagmanager.Parameter{
{
Type: "boolean",
@ -395,25 +397,6 @@ func TestNewClient_Web(t *testing.T) {
// ~ Private methods
// ------------------------------------------------------------------------------------------------
// func eventParameters(event any) []string {
// if event == nil {
// return nil
// }
// var res []string
// v := reflect.TypeOf(event)
//
// if v.Kind() == reflect.Ptr {
// v = v.Elem()
// }
// for i := range v.NumField() {
// tag := v.Field(i).Tag.Get("json")
// if tag != "" && tag != "-" {
// res = append(res, strings.Split(tag, ",")[0])
// }
// }
// return res
// }
func dump(t *testing.T, i interface{ MarshalJSON() ([]byte, error) }) {
t.Helper()
var ret bytes.Buffer