Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@

# End of https://www.toptal.com/developers/gitignore/api/go

/dist
/distsdt
183 changes: 169 additions & 14 deletions cmd/products/products.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ package products

import (
"encoding/json"

"fmt"
"reflect"
"strconv"
"strings"
"time"

shopify "github.com/bold-commerce/go-shopify/v3"
"github.com/cheynewallace/tabby"
"github.com/urfave/cli/v2"

"github.com/ScreenStaring/shopify-dev-tools/cmd"
"github.com/ScreenStaring/shopify-dev-tools/gql"
)

var Cmd cli.Command
Expand All @@ -34,7 +32,76 @@ type listProductOptions struct {
UpdatedAtMin time.Time `url:"updated_at_min,omitempty"`
}

func printJSONL(products []shopify.Product) {
func buildQuery(options listProductOptions) string {
var queryType string
var args []string
var fields string

// Determine fields to return
if len(options.Fields) > 0 {
fields = strings.Join(options.Fields, " ")
} else {
// Default fields that match the original REST API response
fields = "id title handle status vendor productType createdAt updatedAt"
}

if len(options.Ids) > 0 {
// Query specific products by IDs
idStrings := make([]string, len(options.Ids))
for i, id := range options.Ids {
idStrings[i] = fmt.Sprintf(`"gid://shopify/Product/%d"`, id)
}
args = append(args, fmt.Sprintf("ids: [%s]", strings.Join(idStrings, ", ")))
queryType = "products"
} else {
// Query products with filters
if options.Limit > 0 {
args = append(args, fmt.Sprintf("first: %d", options.Limit))
} else {
args = append(args, "first: 10") // Default limit
}

if options.Status != "" {
// Map REST API status values to GraphQL
switch strings.ToUpper(options.Status) {
case "ACTIVE":
args = append(args, `query: "status:active"`)
case "DRAFT":
args = append(args, `query: "status:draft"`)
case "ARCHIVED":
args = append(args, `query: "status:archived"`)
}
}
queryType = "products"
}

argsStr := ""
if len(args) > 0 {
argsStr = fmt.Sprintf("(%s)", strings.Join(args, ", "))
}

if len(options.Ids) > 0 {
return fmt.Sprintf(`{
%s%s {
nodes {
%s
}
}
}`, queryType, argsStr, fields)
} else {
return fmt.Sprintf(`{
%s%s {
edges {
node {
%s
}
}
}
}`, queryType, argsStr, fields)
}
}

func printJSONL(products []map[string]interface{}) {
for _, product := range products {
line, err := json.Marshal(product)
if err != nil {
Expand All @@ -59,7 +126,7 @@ func isFieldToPrint(field string, selectedFields []string) bool {
return false
}

func printFormatted(products []shopify.Product, fieldsToPrint []string) {
func printFormatted(products []map[string]interface{}, fieldsToPrint []string) {
t := tabby.New()
normalizedFieldsToPrint := []string{}

Expand All @@ -68,18 +135,15 @@ func printFormatted(products []shopify.Product, fieldsToPrint []string) {
}

for _, product := range products {
s := reflect.ValueOf(&product).Elem()

for i := 0; i < s.NumField(); i++ {
field := s.Type().Field(i).Name
normalizedField := normalizeField(field)
for key, value := range product {
normalizedField := normalizeField(key)

if len(fieldsToPrint) > 0 {
if isFieldToPrint(normalizedField, normalizedFieldsToPrint) {
t.AddLine(field, s.Field(i).Interface())
t.AddLine(key, value)
}
} else {
t.AddLine(field, s.Field(i).Interface())
t.AddLine(key, value)
}
}

Expand All @@ -89,7 +153,6 @@ func printFormatted(products []shopify.Product, fieldsToPrint []string) {
}

func listProducts(c *cli.Context) error {
var products []shopify.Product
var options listProductOptions

if c.NArg() > 0 {
Expand All @@ -116,11 +179,23 @@ func listProducts(c *cli.Context) error {
options.Fields = strings.Split(c.String("fields"), ",")
}

products, err := cmd.NewShopifyClient(c).Product.List(options)
// Create GraphQL client
shop := c.String("shop")
client := gql.NewClient(shop, cmd.LookupAccessToken(shop, c.String("access-token")), c.String("api-version"))

// Build and execute GraphQL query
query := buildQuery(options)
result, err := client.Query(query)
if err != nil {
return fmt.Errorf("Cannot list products: %s", err)
}

// Parse GraphQL response
products, err := parseProductsResponse(result, len(options.Ids) > 0)
if err != nil {
return fmt.Errorf("Cannot parse products response: %s", err)
}

if c.Bool("jsonl") {
printJSONL(products)
} else {
Expand All @@ -130,6 +205,81 @@ func listProducts(c *cli.Context) error {
return nil
}

func parseProductsResponse(result map[string]interface{}, byIds bool) ([]map[string]interface{}, error) {
var products []map[string]interface{}

// Check for GraphQL errors
if errors, ok := result["errors"]; ok {
return nil, fmt.Errorf("GraphQL errors: %v", errors)
}

// Navigate to the products data
data, ok := result["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response structure: missing data")
}

productsData, ok := data["products"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response structure: missing products")
}

if byIds {
// For queries by ID, products are returned in nodes array
nodes, ok := productsData["nodes"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response structure: missing nodes")
}

for _, node := range nodes {
if product, ok := node.(map[string]interface{}); ok {
// Convert GraphQL ID to numeric ID
if id, exists := product["id"]; exists {
if idStr, ok := id.(string); ok {
if numericId := extractNumericId(idStr); numericId != "" {
product["id"] = numericId
}
}
}
products = append(products, product)
}
}
} else {
// For paginated queries, products are returned in edges array
edges, ok := productsData["edges"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response structure: missing edges")
}

for _, edge := range edges {
if edgeMap, ok := edge.(map[string]interface{}); ok {
if node, ok := edgeMap["node"].(map[string]interface{}); ok {
// Convert GraphQL ID to numeric ID
if id, exists := node["id"]; exists {
if idStr, ok := id.(string); ok {
if numericId := extractNumericId(idStr); numericId != "" {
node["id"] = numericId
}
}
}
products = append(products, node)
}
}
}
}

return products, nil
}

func extractNumericId(gid string) string {
// Extract numeric ID from GraphQL global ID like "gid://shopify/Product/123"
parts := strings.Split(gid, "/")
if len(parts) >= 4 && parts[0] == "gid:" && parts[1] == "" && parts[2] == "shopify" {
return parts[len(parts)-1]
}
return gid
}

func init() {
productFlags := []cli.Flag{
// &cli.StringSliceFlag{
Expand Down Expand Up @@ -157,6 +307,11 @@ func init() {
Aliases: []string{"j"},
Usage: "Output the products in JSONL format",
},
&cli.StringFlag{
Name: "api-version",
Aliases: []string{"a"},
Usage: "API version to use; default is a versionless call",
},
}

Cmd = cli.Command{
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ module github.com/ScreenStaring/shopify-dev-tools
go 1.15

require (
github.com/bold-commerce/go-shopify v2.3.0+incompatible // indirect
github.com/bold-commerce/go-shopify/v3 v3.14.0
github.com/cheynewallace/tabby v1.1.1
github.com/clbanning/mxj v1.8.4
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/shopspring/decimal v1.3.1
github.com/urfave/cli/v2 v2.3.0
)
15 changes: 3 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bold-commerce/go-shopify v2.3.0+incompatible h1:AiedLiOoFWp7iVO8n6JJOM7IEdyU4nLAvUKNM7Hw8b4=
github.com/bold-commerce/go-shopify v2.3.0+incompatible/go.mod h1:R9OKSw+EViwy7MGrAxma3q+Vqq8kMyZu+OJhx/dcw6s=
github.com/bold-commerce/go-shopify/v3 v3.11.0 h1:3FXbtpUmhz91kswPxrIQWFneoxOTW+RWbmhp05bISjI=
github.com/bold-commerce/go-shopify/v3 v3.11.0/go.mod h1:MxKdd8wvTKrRLh19VLZsJwQy0Qw/8GO9TRol+6ErDxg=
github.com/bold-commerce/go-shopify/v3 v3.12.0 h1:zB65ikoXWKOfcQPZGX+1yg4gUDuMrndiqMW53rdrBrs=
github.com/bold-commerce/go-shopify/v3 v3.12.0/go.mod h1:MxKdd8wvTKrRLh19VLZsJwQy0Qw/8GO9TRol+6ErDxg=
github.com/bold-commerce/go-shopify/v3 v3.14.0 h1:YHq1MegncCIOgXNVx4iuftYKe8VsCXn9QjO8O0ggubY=
github.com/bold-commerce/go-shopify/v3 v3.14.0/go.mod h1:qOrEfYoy5RRO/PAq4vGyHW03NZmt2iX/fPGuaZwemtI=
github.com/cheynewallace/tabby v1.1.1 h1:JvUR8waht4Y0S3JF17G6Vhyt+FRhnqVCkk8l4YrOU54=
Expand All @@ -15,29 +9,26 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23 h1:dofHuld+js7eKSemxqTVIo8yRlpRw+H1SdpzZxWruBc=
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
Expand Down
Binary file added sdt
Binary file not shown.