@koda-sl/baker-cli 0.370.0-dev.0cc0d2b86 → 0.370.0-dev.886bf7ec3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/cli.js +403 -20
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3692,16 +3692,21 @@ baker shopify store # name, currency, home countr
|
|
|
3692
3692
|
baker shopify products --search "snowboard" # what the store sells: price, sale price, stock, where it can be bought
|
|
3693
3693
|
baker shopify products --stock out_of_stock # --stock in_stock|partly_out|out_of_stock, --collection <handle|id>
|
|
3694
3694
|
baker shopify products --limit 10 --cursor <c> # page with meta.next_cursor (25 by default, up to 100)
|
|
3695
|
-
baker shopify product <id|handle|link> # one product: options, prices by country, stock,
|
|
3695
|
+
baker shopify product <id|handle|link> # one product: options with buyUrl, prices by country, stock, photos, videos
|
|
3696
|
+
baker shopify product <handle> --content # its whole details: description, custom fields, subscriptions, translations
|
|
3697
|
+
baker shopify product <handle> --field "Size chart" # one custom field, whole
|
|
3698
|
+
baker shopify product <handle> --quantity 2 # buy links that put 2 of the option in the cart
|
|
3696
3699
|
baker shopify product <id> --out-of-stock # only the options that are out of stock (page with --variants-cursor)
|
|
3697
|
-
baker shopify collections --search "summer" # collections, with the handle --collection takes
|
|
3700
|
+
baker shopify collections --search "summer" # collections, with the handle --collection takes (--full: descriptions, images)
|
|
3698
3701
|
baker shopify stock --limit 20 # every product with every option out, and every product with some out
|
|
3699
3702
|
```
|
|
3700
3703
|
|
|
3701
3704
|
**`meta.store`** is on every answer: `connection` (`connected` · `needs_reconnect` · `disconnected`), `catalog` (`starting` · `importing` · `ready` · `failed`, with `importProgress` while importing), `unreadable` (products Baker couldn't read; `unreadableCapped` past 100), `checksFailing` (Baker couldn't check the store for changes lately), `storefront` (`online_store`, or `own` for a store that sells through its own site) and `lastActivity` (when Baker last heard from the store — not a promise the copy is current). `hints[]` turn each of these into what to tell the user.
|
|
3702
3705
|
**`store`:** `--full` adds the admin link and every country. `capped: true` means a count stopped at its ceiling — `outOfStock` and `partlyOut` at 100, `collections` at 500 — so that count reads "that many or more"; a hint says which.
|
|
3703
3706
|
**`products`:** `--search <words>`, `--collection <handle|id>`, `--stock`, `--limit <1-100>`, `--cursor`; `--full` adds vendor, type, option count, photo link, visibility and status. Every price, here and in `product` (`price`, `compareAt`, each option's price, `pricesByCountry`), is one string with its currency beside each amount: `"USD 785.95"`, `"USD 9.95 – USD 49.95"`. A row's `availability` is absent when the product is on the store, `link_only` when it is buyable only through its link, `not_for_sale` (with `status`) when it can't be bought on the online store.
|
|
3704
|
-
**`product`:** positional `<ref>` — the numeric id, the handle, a store product link (`…/products/<handle>`, even an all-digit handle), a Baker Products link, a Shopify admin product link (`admin.shopify.com/store/<shop>/products/<n>`), or `gid://shopify/Product/<n>`. Returns options (50 per page, `--variants-cursor` for more; 250 with `--full`), `outOptions` (up to 50, `outOptionsMore` for the rest), `pricesByCountry` when the store sells at more than one price, `photos[]` (`imageId` + a Baker-hosted `url`, never a Shopify link), `url` (the product's page in Baker) and `storeUrl
|
|
3707
|
+
**`product`:** positional `<ref>` — the numeric id, the handle, a store product link (`…/products/<handle>`, even an all-digit handle), a Baker Products link, a Shopify admin product link (`admin.shopify.com/store/<shop>/products/<n>`), or `gid://shopify/Product/<n>`. Returns options (50 per page, `--variants-cursor` for more; 250 with `--full`), `outOptions` (up to 50, `outOptionsMore` for the rest), `pricesByCountry` when the store sells at more than one price, `photos[]` (`imageId` + a Baker-hosted `url`, never a Shopify link), `url` (the product's page in Baker) and `storeUrl` (the product's page on the store — never a buy button's link). `--full` adds the whole description, SEO, category, the admin link and each option's SKU, barcode, feed id and prices by country. `NOT_FOUND` carries a hint to search instead.
|
|
3708
|
+
|
|
3709
|
+
**Details and buy links (`product`):** every option carries `buyUrl` — the link for a page's buy button, following the store's own setting (`buyDestination`: `checkout`, `store_cart` or `product_page`) — with `buyUrlKind`, `unavailable` when it can't be bought, and `notes` for what a link can't do (`password_protected`, `subscription_only`, `subscription_optional`, `own_storefront`, `details_pending`). `--quantity <1-99>` sets how many it adds. `videos[]` lists the product's videos in the library (`videoId`, `playbackId`); `contentSummary` names what else Baker keeps. `--content` adds `content`: the formatted description, every custom field by its name (long values previewed, `truncated: true`), the rating, subscriptions, bundle, YouTube/Vimeo and 3D links, translations, and `unreadable` for content the store owner hasn't let Baker read yet. `--field <name|namespace.key>` returns one field whole. `--full` adds `--content`, all three links per option and each option's weight, unit price, shipping and tax. `store` adds `buyDestination`, `passwordProtected`, `languages`, `policies` (`--full`: their text, and the store's brand).
|
|
3705
3710
|
**`collections`:** `--search <words>`; each row has `handle`, Shopify's product count and, on a store with an online store, `onStore`.
|
|
3706
3711
|
**`stock`:** `--collection`, `--limit <1-200>` (default 50 per list), `--full` (SKUs and Baker links). Options that keep selling at zero or don't track stock are not out of stock.
|
|
3707
3712
|
|
package/dist/cli.js
CHANGED
|
@@ -5897,7 +5897,11 @@ var REJECTION_CODES = [
|
|
|
5897
5897
|
"missing_identity",
|
|
5898
5898
|
"invalid_timestamp",
|
|
5899
5899
|
"invalid_value",
|
|
5900
|
-
"batch_too_large"
|
|
5900
|
+
"batch_too_large",
|
|
5901
|
+
// Appended, never inserted: the set a client branches on grows, and no code
|
|
5902
|
+
// it already handles changes meaning.
|
|
5903
|
+
"invalid_items",
|
|
5904
|
+
"invalid_consent"
|
|
5901
5905
|
];
|
|
5902
5906
|
var EVENT_INGEST_BATCH_MAX = 500;
|
|
5903
5907
|
var EVENT_INGEST_BODY_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -5910,12 +5914,15 @@ function validateEventName(name) {
|
|
|
5910
5914
|
if (value === "" || value.length > MAX_EVENT_NAME || !EVENT_NAME.test(value)) return "invalid_name";
|
|
5911
5915
|
return null;
|
|
5912
5916
|
}
|
|
5917
|
+
var MAX_ITEM_QUANTITY = 1e6;
|
|
5913
5918
|
var REJECTION_MESSAGES = {
|
|
5914
5919
|
invalid_event_id: "event_id, when sent, must be 1-128 characters of A-Z a-z 0-9 . _ : -",
|
|
5915
5920
|
invalid_name: "event_name is required: up to 80 characters of letters (any language), digits, spaces, . _ and -, starting with a letter or a digit",
|
|
5916
5921
|
missing_identity: "user_data named nobody, and no url named a page",
|
|
5917
5922
|
invalid_timestamp: "event_time must be an ISO-8601 instant with an offset or Unix seconds, and not in the future",
|
|
5918
5923
|
invalid_value: "value and currency go together: a non-negative decimal and an ISO-4217 code",
|
|
5924
|
+
invalid_items: "each item names a product_id, variant_id or feed_id; quantity is a whole number from 1 to 1000000; price is a non-negative decimal sent with a currency",
|
|
5925
|
+
invalid_consent: "consent.analytics and consent.marketing, when sent, are granted or denied",
|
|
5919
5926
|
batch_too_large: `a request carries at most ${EVENT_INGEST_BATCH_MAX} events and ${EVENT_INGEST_BODY_MAX_BYTES / (1024 * 1024)} MB`
|
|
5920
5927
|
};
|
|
5921
5928
|
|
|
@@ -7294,7 +7301,8 @@ var TRIGGER_KINDS = {
|
|
|
7294
7301
|
"utm_source",
|
|
7295
7302
|
"utm_medium",
|
|
7296
7303
|
"utm_campaign",
|
|
7297
|
-
"properties.<name>"
|
|
7304
|
+
"properties.<name>",
|
|
7305
|
+
"items"
|
|
7298
7306
|
],
|
|
7299
7307
|
keywords: ["purchase", "signup", "conversion", "analytics", "tracking", "hubspot", "crm", "deal", "funnel", "lead"]
|
|
7300
7308
|
},
|
|
@@ -8136,7 +8144,8 @@ var videoSourceSchema = z29.enum([
|
|
|
8136
8144
|
"twitter",
|
|
8137
8145
|
"vimeo",
|
|
8138
8146
|
"vk",
|
|
8139
|
-
"youtube"
|
|
8147
|
+
"youtube",
|
|
8148
|
+
"shopify"
|
|
8140
8149
|
]);
|
|
8141
8150
|
var videoTranscriptSegmentSchema = z29.object({
|
|
8142
8151
|
text: z29.string(),
|
|
@@ -14905,8 +14914,22 @@ var ANALYTICS_EVENT_TYPES = [
|
|
|
14905
14914
|
* page, `ingest` for a third party's server — and every report that needs to
|
|
14906
14915
|
* tell them apart filters on that.
|
|
14907
14916
|
*/
|
|
14908
|
-
"custom"
|
|
14917
|
+
"custom",
|
|
14918
|
+
/**
|
|
14919
|
+
* A fact about the business that names no person — a product changed, a
|
|
14920
|
+
* variant went out of stock.
|
|
14921
|
+
*
|
|
14922
|
+
* Only a commerce door writes it (`ingestShopifyFacts`), and it is a type of
|
|
14923
|
+
* its own rather than a `custom` event with no identity for one reason:
|
|
14924
|
+
* every report that lists named events whitelists `custom`, so an
|
|
14925
|
+
* operational fact would otherwise appear in the conversion picker, the
|
|
14926
|
+
* funnel and the sends. Here it reaches the raw feed and the agents that
|
|
14927
|
+
* listen for its name, and no person-level report. The row builder blanks
|
|
14928
|
+
* every identity column on it, whatever it carried.
|
|
14929
|
+
*/
|
|
14930
|
+
"operational"
|
|
14909
14931
|
];
|
|
14932
|
+
var EVENT_ITEMS_MAX = 250;
|
|
14910
14933
|
var ANALYTICS_DEVICE_TYPES = ["desktop", "mobile", "tablet", "bot", "unknown"];
|
|
14911
14934
|
var ANALYTICS_BEACON_BATCH_MAX = 50;
|
|
14912
14935
|
|
|
@@ -30919,6 +30942,48 @@ var EVENT_FIELDS = [
|
|
|
30919
30942
|
name: "properties",
|
|
30920
30943
|
need: "optional",
|
|
30921
30944
|
takes: "A flat object of strings \u2014 anything you want to break the event down by later."
|
|
30945
|
+
},
|
|
30946
|
+
{
|
|
30947
|
+
name: "items",
|
|
30948
|
+
need: "optional",
|
|
30949
|
+
takes: "What was bought, one entry per product line \u2014 for a purchase, a cart or a refund.",
|
|
30950
|
+
note: `Each line names its product by at least one of the ids below. value still carries the whole event's money. An order with more than ${EVENT_ITEMS_MAX} lines keeps the first ${EVENT_ITEMS_MAX}.`
|
|
30951
|
+
},
|
|
30952
|
+
{
|
|
30953
|
+
name: "product_id, variant_id, feed_id",
|
|
30954
|
+
need: "conditional",
|
|
30955
|
+
under: true,
|
|
30956
|
+
takes: "Your store's product id, its variant id, or the id the product has in your ads catalog. At least one per line, up to 255 characters each."
|
|
30957
|
+
},
|
|
30958
|
+
{
|
|
30959
|
+
name: "quantity",
|
|
30960
|
+
need: "optional",
|
|
30961
|
+
under: true,
|
|
30962
|
+
takes: `How many: a whole number from 1 to ${MAX_ITEM_QUANTITY.toLocaleString("en-US")}.`
|
|
30963
|
+
},
|
|
30964
|
+
{
|
|
30965
|
+
name: "price",
|
|
30966
|
+
need: "optional",
|
|
30967
|
+
under: true,
|
|
30968
|
+
takes: "The price of one unit, in the event's currency \u2014 so it needs currency. A decimal like value."
|
|
30969
|
+
},
|
|
30970
|
+
{
|
|
30971
|
+
name: "consent",
|
|
30972
|
+
need: "optional",
|
|
30973
|
+
takes: "What the person allowed, if your site asked them.",
|
|
30974
|
+
note: "Send it whenever you know it. A person who said no to marketing is still counted in your reports, and is never matched or sent to an ad platform."
|
|
30975
|
+
},
|
|
30976
|
+
{
|
|
30977
|
+
name: "analytics",
|
|
30978
|
+
need: "optional",
|
|
30979
|
+
under: true,
|
|
30980
|
+
takes: "granted or denied \u2014 whether they allowed being measured. Denied keeps the event and forgets who it was."
|
|
30981
|
+
},
|
|
30982
|
+
{
|
|
30983
|
+
name: "marketing",
|
|
30984
|
+
need: "optional",
|
|
30985
|
+
under: true,
|
|
30986
|
+
takes: "granted or denied \u2014 whether they allowed their visit to be used for advertising."
|
|
30922
30987
|
}
|
|
30923
30988
|
];
|
|
30924
30989
|
var RESPONSE_FIELDS = [
|
|
@@ -31248,7 +31313,37 @@ var ingestEventSchema = z39.strictObject({
|
|
|
31248
31313
|
* left alone: masking one would rename the field and could collide with a
|
|
31249
31314
|
* sibling.
|
|
31250
31315
|
*/
|
|
31251
|
-
properties: z39.record(z39.string(), z39.string()).nullish()
|
|
31316
|
+
properties: z39.record(z39.string(), z39.string()).nullish(),
|
|
31317
|
+
/**
|
|
31318
|
+
* What was bought, one entry per line — for a purchase, a cart, a refund.
|
|
31319
|
+
*
|
|
31320
|
+
* Flat and Meta-like for the same reason as the rest of this body: it is
|
|
31321
|
+
* Meta's `contents` (`id`, `quantity`, `item_price`) and GA4's `items`
|
|
31322
|
+
* (`item_id`, `quantity`, `price`) under one spelling a sender can map from
|
|
31323
|
+
* either. A line names what it is by any of three ids — the store's product,
|
|
31324
|
+
* its variant, or the id the product has in an ads catalog. `price` is the
|
|
31325
|
+
* *unit* price in the event's `currency`; `value` stays the whole event's
|
|
31326
|
+
* money. Structure only here; `validateItems` judges each line.
|
|
31327
|
+
*/
|
|
31328
|
+
items: z39.array(
|
|
31329
|
+
z39.strictObject({
|
|
31330
|
+
product_id: z39.string().nullish(),
|
|
31331
|
+
variant_id: z39.string().nullish(),
|
|
31332
|
+
feed_id: z39.string().nullish(),
|
|
31333
|
+
quantity: z39.union([z39.string(), z39.number()]).nullish(),
|
|
31334
|
+
price: z39.union([z39.string(), z39.number()]).nullish()
|
|
31335
|
+
})
|
|
31336
|
+
).nullish(),
|
|
31337
|
+
/**
|
|
31338
|
+
* What the person allowed, each `granted` or `denied`: `analytics` (being
|
|
31339
|
+
* measured at all) and `marketing` (being matched and sent to an ad
|
|
31340
|
+
* platform). Absent means the sender did not say. Enforced before anything
|
|
31341
|
+
* is stored — see `./consent`. `validateConsent` judges the values.
|
|
31342
|
+
*/
|
|
31343
|
+
consent: z39.strictObject({
|
|
31344
|
+
analytics: z39.string().nullish(),
|
|
31345
|
+
marketing: z39.string().nullish()
|
|
31346
|
+
}).nullish()
|
|
31252
31347
|
});
|
|
31253
31348
|
var ingestBatchSchema = z39.strictObject({
|
|
31254
31349
|
events: z39.array(ingestEventSchema).min(1).max(EVENT_INGEST_BATCH_MAX)
|
|
@@ -64540,6 +64635,15 @@ var shopifyStoreConnectionSchema = z50.enum(["connected", "needs_reconnect", "di
|
|
|
64540
64635
|
var shopifyCatalogStateSchema = z50.enum(["starting", "importing", "ready", "failed"]);
|
|
64541
64636
|
var shopifyStorefrontSchema = z50.enum(["online_store", "own"]);
|
|
64542
64637
|
var shopifyAvailabilitySchema = z50.enum(["link_only", "not_for_sale"]);
|
|
64638
|
+
var shopifyBuyDestinationSchema = z50.enum(["checkout", "store_cart", "product_page"]);
|
|
64639
|
+
var shopifyBuyNoteSchema = z50.enum([
|
|
64640
|
+
"subscription_only",
|
|
64641
|
+
"subscription_optional",
|
|
64642
|
+
"password_protected",
|
|
64643
|
+
"own_storefront",
|
|
64644
|
+
"details_pending"
|
|
64645
|
+
]);
|
|
64646
|
+
var shopifyUnreadablePartSchema = z50.enum(["metaobjects", "definitions", "languages", "policies"]);
|
|
64543
64647
|
var shopifyStoreStateSchema = z50.object({
|
|
64544
64648
|
name: z50.string(),
|
|
64545
64649
|
connection: shopifyStoreConnectionSchema,
|
|
@@ -64564,13 +64668,26 @@ var shopifyProductsRequestSchema = z50.object({
|
|
|
64564
64668
|
cursor: z50.string().optional(),
|
|
64565
64669
|
full: flag.optional()
|
|
64566
64670
|
});
|
|
64671
|
+
var SHOPIFY_MAX_BUY_QUANTITY = 99;
|
|
64567
64672
|
var shopifyProductRequestSchema = z50.object({
|
|
64568
64673
|
ref: z50.string().min(1).max(500),
|
|
64569
64674
|
variantsCursor: z50.string().optional(),
|
|
64570
64675
|
outOfStock: flag.optional(),
|
|
64676
|
+
full: flag.optional(),
|
|
64677
|
+
/** The product's whole details: formatted description, custom fields, subscriptions, translations. */
|
|
64678
|
+
content: flag.optional(),
|
|
64679
|
+
/** One custom field whole, by its name or `namespace.key`. */
|
|
64680
|
+
field: z50.string().min(1).max(200).optional(),
|
|
64681
|
+
/** With `field`: the option (its `variantId`) whose field to return, when options share one. */
|
|
64682
|
+
variant: z50.string().regex(/^\d{1,30}$/, "an option's variantId").optional(),
|
|
64683
|
+
/** How many of the option each buy link puts in the cart (1-99, default 1). */
|
|
64684
|
+
quantity: z50.coerce.number().int().min(1).max(SHOPIFY_MAX_BUY_QUANTITY).optional()
|
|
64685
|
+
});
|
|
64686
|
+
var shopifyCollectionsRequestSchema = z50.object({
|
|
64687
|
+
search: z50.string().max(200).optional(),
|
|
64688
|
+
/** Each collection's description, image and SEO. */
|
|
64571
64689
|
full: flag.optional()
|
|
64572
64690
|
});
|
|
64573
|
-
var shopifyCollectionsRequestSchema = z50.object({ search: z50.string().max(200).optional() });
|
|
64574
64691
|
var shopifyStockRequestSchema = z50.object({
|
|
64575
64692
|
collection: z50.string().min(1).max(500).optional(),
|
|
64576
64693
|
limit: z50.coerce.number().int().min(1).max(200).optional(),
|
|
@@ -64602,8 +64719,35 @@ var shopifyStoreSummarySchema = z50.object({
|
|
|
64602
64719
|
})
|
|
64603
64720
|
).optional(),
|
|
64604
64721
|
productsUrl: z50.string(),
|
|
64722
|
+
/** Where the buy links send a shopper (S3b): the store's own setting, checkout unless changed. */
|
|
64723
|
+
buyDestination: shopifyBuyDestinationSchema,
|
|
64724
|
+
/** The store asks for a password before anyone can see it: a buy link meets it first. */
|
|
64725
|
+
passwordProtected: z50.literal(true).optional(),
|
|
64726
|
+
/** The store's published languages, primary first. */
|
|
64727
|
+
languages: z50.array(z50.object({ locale: z50.string(), name: z50.string(), primary: z50.boolean() })).optional(),
|
|
64728
|
+
policies: z50.array(
|
|
64729
|
+
z50.object({
|
|
64730
|
+
type: z50.string(),
|
|
64731
|
+
title: z50.string(),
|
|
64732
|
+
url: z50.string().optional(),
|
|
64733
|
+
// --full
|
|
64734
|
+
text: z50.string().optional()
|
|
64735
|
+
})
|
|
64736
|
+
).optional(),
|
|
64737
|
+
/** Store content Baker can't read until the store owner accepts Baker's new permissions. */
|
|
64738
|
+
unreadable: z50.array(shopifyUnreadablePartSchema).optional(),
|
|
64605
64739
|
// --full
|
|
64606
|
-
adminUrl: z50.string().optional()
|
|
64740
|
+
adminUrl: z50.string().optional(),
|
|
64741
|
+
brand: z50.object({
|
|
64742
|
+
slogan: z50.string().optional(),
|
|
64743
|
+
shortDescription: z50.string().optional(),
|
|
64744
|
+
logoUrl: z50.string().optional(),
|
|
64745
|
+
squareLogoUrl: z50.string().optional(),
|
|
64746
|
+
coverImageUrl: z50.string().optional(),
|
|
64747
|
+
colors: z50.array(
|
|
64748
|
+
z50.object({ role: z50.string(), background: z50.string().optional(), foreground: z50.string().optional() })
|
|
64749
|
+
)
|
|
64750
|
+
}).optional()
|
|
64607
64751
|
});
|
|
64608
64752
|
var shopifyStoreResponseSchema = z50.object({
|
|
64609
64753
|
store: shopifyStoreStateSchema,
|
|
@@ -64645,7 +64789,20 @@ var shopifyVariantSchema = z50.object({
|
|
|
64645
64789
|
compareAtPrice: shopifyPriceSchema.optional(),
|
|
64646
64790
|
stock: shopifyOptionStockSchema,
|
|
64647
64791
|
quantity: z50.number().optional(),
|
|
64792
|
+
/** The link for the page's buy button: never build a Shopify link by hand. Absent when the option can't be bought. */
|
|
64793
|
+
buyUrl: z50.string().optional(),
|
|
64794
|
+
buyUrlKind: shopifyBuyDestinationSchema.optional(),
|
|
64795
|
+
/** Why there is no buy link. */
|
|
64796
|
+
unavailable: z50.enum(["not_for_sale", "out_of_stock"]).optional(),
|
|
64797
|
+
notes: z50.array(shopifyBuyNoteSchema).optional(),
|
|
64648
64798
|
// --full
|
|
64799
|
+
checkout: z50.string().optional(),
|
|
64800
|
+
storeCart: z50.string().optional(),
|
|
64801
|
+
productPage: z50.string().optional(),
|
|
64802
|
+
weight: z50.string().optional(),
|
|
64803
|
+
unitPrice: z50.string().optional(),
|
|
64804
|
+
requiresShipping: z50.boolean().optional(),
|
|
64805
|
+
taxable: z50.boolean().optional(),
|
|
64649
64806
|
sku: z50.string().optional(),
|
|
64650
64807
|
barcode: z50.string().optional(),
|
|
64651
64808
|
selectedOptions: z50.array(z50.object({ name: z50.string(), value: z50.string() })).optional(),
|
|
@@ -64653,12 +64810,101 @@ var shopifyVariantSchema = z50.object({
|
|
|
64653
64810
|
inventoryPolicy: z50.enum(["DENY", "CONTINUE"]).optional(),
|
|
64654
64811
|
prices: z50.array(countryPriceSchema).optional()
|
|
64655
64812
|
});
|
|
64813
|
+
var shopifyProductVideoSchema = z50.object({
|
|
64814
|
+
videoId: z50.string(),
|
|
64815
|
+
playbackId: z50.string(),
|
|
64816
|
+
alt: z50.string().optional(),
|
|
64817
|
+
/** Seconds. */
|
|
64818
|
+
duration: z50.number().optional()
|
|
64819
|
+
});
|
|
64820
|
+
var shopifyFieldRefSchema = z50.object({
|
|
64821
|
+
kind: z50.enum(["image", "file", "video", "product", "variant", "collection", "metaobject", "other"]),
|
|
64822
|
+
id: z50.string(),
|
|
64823
|
+
url: z50.string().optional(),
|
|
64824
|
+
mimeType: z50.string().optional(),
|
|
64825
|
+
alt: z50.string().optional(),
|
|
64826
|
+
/** A video's preview image; its `url` is the video file itself. */
|
|
64827
|
+
previewUrl: z50.string().optional(),
|
|
64828
|
+
/** A product's or a collection's title, from Baker's copy. */
|
|
64829
|
+
title: z50.string().optional(),
|
|
64830
|
+
/** An image brought into the library: its imageId, to place like the product's photos. */
|
|
64831
|
+
imageId: z50.string().optional(),
|
|
64832
|
+
/** A custom content entry (a size chart, a care guide): its name and fields. */
|
|
64833
|
+
entry: z50.object({
|
|
64834
|
+
name: z50.string(),
|
|
64835
|
+
fields: z50.array(z50.object({ name: z50.string(), text: z50.string(), truncated: z50.literal(true).optional() }))
|
|
64836
|
+
}).optional()
|
|
64837
|
+
});
|
|
64838
|
+
var shopifyProductFieldSchema = z50.object({
|
|
64839
|
+
name: z50.string(),
|
|
64840
|
+
/** `namespace.key`: what `--field` takes when two fields share a name. */
|
|
64841
|
+
key: z50.string(),
|
|
64842
|
+
type: z50.string(),
|
|
64843
|
+
owner: z50.enum(["product", "option"]),
|
|
64844
|
+
variantId: z50.string().optional(),
|
|
64845
|
+
text: z50.string(),
|
|
64846
|
+
html: z50.string().optional(),
|
|
64847
|
+
refs: z50.array(shopifyFieldRefSchema).optional(),
|
|
64848
|
+
refsMore: z50.number().optional(),
|
|
64849
|
+
/** The text or the list is longer than shown: `--field <key>` returns it whole. */
|
|
64850
|
+
truncated: z50.literal(true).optional(),
|
|
64851
|
+
/** The store owner hasn't let Baker read what this field points at yet. */
|
|
64852
|
+
unreadable: z50.literal(true).optional()
|
|
64853
|
+
});
|
|
64854
|
+
var shopifyProductContentSchema = z50.object({
|
|
64855
|
+
descriptionHtml: z50.string(),
|
|
64856
|
+
descriptionTruncated: z50.literal(true).optional(),
|
|
64857
|
+
category: z50.string().optional(),
|
|
64858
|
+
giftCard: z50.literal(true).optional(),
|
|
64859
|
+
fields: z50.array(shopifyProductFieldSchema),
|
|
64860
|
+
rating: z50.object({ value: z50.number(), scaleMax: z50.number(), count: z50.number().optional() }).optional(),
|
|
64861
|
+
/** Subscriptions the product can be bought with; `subscriptionOnly` when only by subscription. */
|
|
64862
|
+
subscriptions: z50.array(
|
|
64863
|
+
z50.object({ name: z50.string(), plans: z50.array(z50.object({ name: z50.string(), category: z50.string().optional() })) })
|
|
64864
|
+
).optional(),
|
|
64865
|
+
subscriptionOnly: z50.literal(true).optional(),
|
|
64866
|
+
/** YouTube or Vimeo videos and 3D models: links with their preview image. */
|
|
64867
|
+
mediaLinks: z50.array(
|
|
64868
|
+
z50.object({
|
|
64869
|
+
kind: z50.enum(["external_video", "model_3d"]),
|
|
64870
|
+
url: z50.string(),
|
|
64871
|
+
embedUrl: z50.string().optional(),
|
|
64872
|
+
previewUrl: z50.string().optional(),
|
|
64873
|
+
alt: z50.string().optional()
|
|
64874
|
+
})
|
|
64875
|
+
),
|
|
64876
|
+
bundle: z50.array(z50.object({ productId: z50.string(), title: z50.string().optional(), quantity: z50.number() })).optional(),
|
|
64877
|
+
combinedListing: z50.object({ role: z50.string(), parentProductId: z50.string().optional(), childProductIds: z50.array(z50.string()) }).optional(),
|
|
64878
|
+
translations: z50.array(
|
|
64879
|
+
z50.object({
|
|
64880
|
+
locale: z50.string(),
|
|
64881
|
+
title: z50.string().optional(),
|
|
64882
|
+
description: z50.string().optional(),
|
|
64883
|
+
seoTitle: z50.string().optional(),
|
|
64884
|
+
seoDescription: z50.string().optional(),
|
|
64885
|
+
options: z50.array(
|
|
64886
|
+
z50.object({
|
|
64887
|
+
name: z50.string(),
|
|
64888
|
+
translatedName: z50.string().optional(),
|
|
64889
|
+
values: z50.array(z50.object({ value: z50.string(), translated: z50.string().optional() }))
|
|
64890
|
+
})
|
|
64891
|
+
),
|
|
64892
|
+
truncated: z50.literal(true).optional()
|
|
64893
|
+
})
|
|
64894
|
+
),
|
|
64895
|
+
/** Content the store owner hasn't let Baker read yet. */
|
|
64896
|
+
unreadable: z50.array(shopifyUnreadablePartSchema).optional(),
|
|
64897
|
+
/** Baker hasn't read this product's details yet, or Shopify didn't send some of them. */
|
|
64898
|
+
pending: z50.literal(true).optional(),
|
|
64899
|
+
failure: z50.string().optional()
|
|
64900
|
+
});
|
|
64656
64901
|
var shopifyProductDetailSchema = z50.object({
|
|
64657
64902
|
productId: z50.string(),
|
|
64658
64903
|
title: z50.string(),
|
|
64659
64904
|
handle: z50.string(),
|
|
64660
64905
|
/** The product's page in Baker — the link to share with the user. */
|
|
64661
64906
|
url: z50.string(),
|
|
64907
|
+
/** The product's page on the online store: to look at, never a buy button's link (that is each option's `buyUrl`). */
|
|
64662
64908
|
storeUrl: z50.string().optional(),
|
|
64663
64909
|
availability: shopifyAvailabilitySchema.optional(),
|
|
64664
64910
|
status: shopifyProductStatusSchema.optional(),
|
|
@@ -64690,6 +64936,19 @@ var shopifyProductDetailSchema = z50.object({
|
|
|
64690
64936
|
).optional(),
|
|
64691
64937
|
/** When Baker last read this product from the store. */
|
|
64692
64938
|
observedAt: z50.string(),
|
|
64939
|
+
/** What else Baker keeps about it, in a line: "formatted description, 2 videos, Size chart, 4.7★ from 120 reviews". */
|
|
64940
|
+
contentSummary: z50.string().optional(),
|
|
64941
|
+
/** Its Shopify videos in the video library: place one with `<Video playbackId=…/>`. */
|
|
64942
|
+
videos: z50.array(shopifyProductVideoSchema),
|
|
64943
|
+
videosAdding: z50.number(),
|
|
64944
|
+
videosNotBrought: z50.number(),
|
|
64945
|
+
/** Where the buy links send a shopper, and how many each puts in the cart. */
|
|
64946
|
+
buyDestination: shopifyBuyDestinationSchema,
|
|
64947
|
+
quantity: z50.number(),
|
|
64948
|
+
/** --content (or --full): the whole of it. */
|
|
64949
|
+
content: shopifyProductContentSchema.optional(),
|
|
64950
|
+
/** --field: one custom field, whole. */
|
|
64951
|
+
field: shopifyProductFieldSchema.optional(),
|
|
64693
64952
|
// --full
|
|
64694
64953
|
visibility: shopifyVisibilitySchema.optional(),
|
|
64695
64954
|
seo: z50.object({ title: z50.string().optional(), description: z50.string().optional() }).optional(),
|
|
@@ -64706,7 +64965,11 @@ var shopifyCollectionRowSchema = z50.object({
|
|
|
64706
64965
|
handle: z50.string(),
|
|
64707
64966
|
/** Shopify's count at Baker's last read. */
|
|
64708
64967
|
products: z50.number(),
|
|
64709
|
-
onStore: z50.boolean().optional()
|
|
64968
|
+
onStore: z50.boolean().optional(),
|
|
64969
|
+
// --full
|
|
64970
|
+
description: z50.string().optional(),
|
|
64971
|
+
imageUrl: z50.string().optional(),
|
|
64972
|
+
seo: z50.object({ title: z50.string().optional(), description: z50.string().optional() }).optional()
|
|
64710
64973
|
});
|
|
64711
64974
|
var shopifyCollectionsResponseSchema = z50.object({
|
|
64712
64975
|
store: shopifyStoreStateSchema,
|
|
@@ -64848,8 +65111,58 @@ function stockHintsFor(product) {
|
|
|
64848
65111
|
}
|
|
64849
65112
|
return hints2;
|
|
64850
65113
|
}
|
|
65114
|
+
var BUY_NOTES = {
|
|
65115
|
+
password_protected: "The store asks for a password before anyone can see it: a shopper following a buy link meets it first \u2014 tell the user before sending traffic.",
|
|
65116
|
+
subscription_optional: "It can also be bought by subscription, but a buy link can only add it once \u2014 say so if the page promotes the subscription.",
|
|
65117
|
+
own_storefront: "The store sells through its own storefront: buy links go straight to Shopify's checkout.",
|
|
65118
|
+
details_pending: "Baker hasn't read this product's details yet (subscriptions among them): its buy links may change.",
|
|
65119
|
+
subscription_only: "It's sold only by subscription, which a buy link can't carry: its buy link is the product page, where the shopper picks the plan."
|
|
65120
|
+
};
|
|
65121
|
+
function buyHints(product) {
|
|
65122
|
+
const hints2 = [];
|
|
65123
|
+
if (product.variants.some((variant) => variant.buyUrl)) {
|
|
65124
|
+
hints2.push(
|
|
65125
|
+
`Use each option's \`buyUrl\` for the page's buy button \u2014 never build a Shopify link yourself (it follows the store's setting: ${product.buyDestination.replace("_", " ")}; \`--quantity <n>\` sets how many).`,
|
|
65126
|
+
"Keep that `buyUrl` on the button: never `storeUrl` (a page to look at, not a buy link), and never drop or replace the buy button to get a page past its checks. The options to sell are `variants[]` alone \u2014 one buy button per option, each with its own `buyUrl`, never a size or colour picker made up from a size chart or other details. When the shopper must choose on the store, link the option's `productPage` (from `--full`)."
|
|
65127
|
+
);
|
|
65128
|
+
}
|
|
65129
|
+
const notes = new Set(product.variants.flatMap((variant) => variant.notes ?? []));
|
|
65130
|
+
for (const note of notes) hints2.push(BUY_NOTES[note]);
|
|
65131
|
+
return hints2;
|
|
65132
|
+
}
|
|
65133
|
+
function contentHints(product) {
|
|
65134
|
+
const hints2 = [];
|
|
65135
|
+
const [video] = product.videos;
|
|
65136
|
+
if (video) {
|
|
65137
|
+
hints2.push(
|
|
65138
|
+
`Its videos are already in the video library: place one with \`<Video playbackId="${video.playbackId}" />\` \u2014 never a Shopify video link.`
|
|
65139
|
+
);
|
|
65140
|
+
}
|
|
65141
|
+
if (product.videosNotBrought > 0) {
|
|
65142
|
+
hints2.push(
|
|
65143
|
+
`${product.videosNotBrought} more videos stay in Shopify (Baker keeps up to 5 per product); use the ones listed.`
|
|
65144
|
+
);
|
|
65145
|
+
}
|
|
65146
|
+
if (product.videosAdding > 0) hints2.push(`${product.videosAdding} videos are still being added.`);
|
|
65147
|
+
if (product.contentSummary && !product.content) {
|
|
65148
|
+
hints2.push(
|
|
65149
|
+
"For a landing or an ad, read the whole of it with `--content` (formatted description, custom fields, subscriptions, translations)."
|
|
65150
|
+
);
|
|
65151
|
+
}
|
|
65152
|
+
const truncated = product.content?.fields.find((field) => field.truncated);
|
|
65153
|
+
if (truncated) {
|
|
65154
|
+
const option = truncated.variantId ? ` --variant ${truncated.variantId}` : "";
|
|
65155
|
+
hints2.push(`Some fields are shown in part: read one whole with \`--field ${truncated.key}${option}\`.`);
|
|
65156
|
+
}
|
|
65157
|
+
if (product.content?.unreadable?.length || product.content?.fields.some((field) => field.unreadable)) {
|
|
65158
|
+
hints2.push(
|
|
65159
|
+
"Some content isn't readable yet: ask the user to open Baker in their Shopify admin and accept the new permissions."
|
|
65160
|
+
);
|
|
65161
|
+
}
|
|
65162
|
+
return hints2;
|
|
65163
|
+
}
|
|
64851
65164
|
function productHints(product, store) {
|
|
64852
|
-
const hints2 = [...photoHints(product), ...stockHintsFor(product)];
|
|
65165
|
+
const hints2 = [...photoHints(product), ...stockHintsFor(product), ...buyHints(product), ...contentHints(product)];
|
|
64853
65166
|
if (product.availability === "not_for_sale") {
|
|
64854
65167
|
hints2.push(
|
|
64855
65168
|
`Can't be bought on the online store (${(product.status ?? "hidden").toLowerCase()}): a page or ad linking to it sends people to a page they can't buy from.`
|
|
@@ -64899,6 +65212,16 @@ function stockHints(answer, limit) {
|
|
|
64899
65212
|
}
|
|
64900
65213
|
function storeHints(summary) {
|
|
64901
65214
|
const hints2 = ["Share the Products page link (productsUrl) when the user wants to browse the catalog."];
|
|
65215
|
+
if (summary.unreadable?.length) {
|
|
65216
|
+
hints2.push(
|
|
65217
|
+
"Some of the store's content isn't readable yet: ask the user to open Baker in their Shopify admin and accept the new permissions."
|
|
65218
|
+
);
|
|
65219
|
+
}
|
|
65220
|
+
if (summary.passwordProtected) {
|
|
65221
|
+
hints2.push(
|
|
65222
|
+
"The store asks for a password before anyone can see it: shoppers from a landing or an ad meet it first."
|
|
65223
|
+
);
|
|
65224
|
+
}
|
|
64902
65225
|
if (summary.capped) {
|
|
64903
65226
|
const atCeiling = [
|
|
64904
65227
|
summary.outOfStock >= SHOPIFY_STOCK_COUNT_CAP ? `${SHOPIFY_STOCK_COUNT_CAP} or more products out of stock` : null,
|
|
@@ -64972,8 +65295,13 @@ var fullArg = {
|
|
|
64972
65295
|
};
|
|
64973
65296
|
registerSchema({
|
|
64974
65297
|
command: "shopify.store",
|
|
64975
|
-
description: 'Start here for the connected Shopify store: its name, currency, home country, how many products and collections it has, how many products are out of stock or partly out, the prices by country it sells at (only when it has more than one), and the link to the Products page in Baker. `capped: true` means a count stopped at its ceiling (100 out of stock or partly out, 500 collections): say "100 or more", never the number as exact. `meta.store` says whether Baker\'s copy is complete and whether Baker can still reach the store.',
|
|
64976
|
-
args: {
|
|
65298
|
+
description: 'Start here for the connected Shopify store: its name, currency, home country, how many products and collections it has, how many products are out of stock or partly out, the prices by country it sells at (only when it has more than one), where its buy links send a shopper (`buyDestination`: checkout, store_cart or product_page), its languages and policies, whether it asks for a password, and the link to the Products page in Baker. `capped: true` means a count stopped at its ceiling (100 out of stock or partly out, 500 collections): say "100 or more", never the number as exact. `meta.store` says whether Baker\'s copy is complete and whether Baker can still reach the store.',
|
|
65299
|
+
args: {
|
|
65300
|
+
full: {
|
|
65301
|
+
...fullArg,
|
|
65302
|
+
description: "Add the store's products page in Shopify admin, every country, each policy's text and the store's brand"
|
|
65303
|
+
}
|
|
65304
|
+
}
|
|
64977
65305
|
});
|
|
64978
65306
|
registerSchema({
|
|
64979
65307
|
command: "shopify.products",
|
|
@@ -65002,7 +65330,7 @@ registerSchema({
|
|
|
65002
65330
|
});
|
|
65003
65331
|
registerSchema({
|
|
65004
65332
|
command: "shopify.product",
|
|
65005
|
-
description: "One product of the connected store \u2014 accepts its id, its handle, a link to it on the store, in Baker or in Shopify admin. Returns its options with price and
|
|
65333
|
+
description: "One product of the connected store \u2014 accepts its id, its handle, a link to it on the store, in Baker or in Shopify admin. Returns its options with price, stock and `buyUrl` (the link for a page's buy button, one per option \u2014 never build a Shopify link yourself, never use `storeUrl` for it), prices by country, the options out of stock, its collections, its photos' library imageIds to place on a landing (`baker images download <imageIds> --out src/pages/<page>/_images/`) or pass to a creative, its videos' playbackIds (`<Video playbackId=\u2026/>`), and `contentSummary`: what else Baker keeps about it. Start here for a landing from a product: add `--content` for the formatted description, every custom field by its name (a size chart, care instructions), the rating, subscriptions and translations; `--field <name>` returns one field whole. `--out-of-stock` lists only the options out of stock.",
|
|
65006
65334
|
args: {
|
|
65007
65335
|
ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
65008
65336
|
"variants-cursor": {
|
|
@@ -65016,16 +65344,40 @@ registerSchema({
|
|
|
65016
65344
|
required: false,
|
|
65017
65345
|
default: false
|
|
65018
65346
|
},
|
|
65347
|
+
content: {
|
|
65348
|
+
type: "boolean",
|
|
65349
|
+
description: "Its whole details: formatted description, custom fields by name (long ones previewed), rating, subscriptions, YouTube/3D links, translations",
|
|
65350
|
+
required: false,
|
|
65351
|
+
default: false
|
|
65352
|
+
},
|
|
65353
|
+
field: {
|
|
65354
|
+
type: "string",
|
|
65355
|
+
description: 'One custom field whole, by its name or its key (e.g. "Size chart" or custom.size_chart)',
|
|
65356
|
+
required: false
|
|
65357
|
+
},
|
|
65358
|
+
variant: {
|
|
65359
|
+
type: "string",
|
|
65360
|
+
description: "With --field: the option whose field to return, by its variantId (options share their fields, e.g. --field custom.fit --variant 102)",
|
|
65361
|
+
required: false
|
|
65362
|
+
},
|
|
65363
|
+
quantity: {
|
|
65364
|
+
type: "number",
|
|
65365
|
+
description: "How many of the option each buy link puts in the cart, 1-99 (default 1)",
|
|
65366
|
+
required: false
|
|
65367
|
+
},
|
|
65019
65368
|
full: {
|
|
65020
65369
|
...fullArg,
|
|
65021
|
-
description: "
|
|
65370
|
+
description: "Its details (as --content), whole description, SEO, category, admin link, 250 options per page with SKU, prices by country, every buy link and each option's weight, unit price, shipping and tax"
|
|
65022
65371
|
}
|
|
65023
65372
|
}
|
|
65024
65373
|
});
|
|
65025
65374
|
registerSchema({
|
|
65026
65375
|
command: "shopify.collections",
|
|
65027
|
-
description: "The connected store's collections, with the handle `--collection` takes and how many products Shopify counts in each.",
|
|
65028
|
-
args: {
|
|
65376
|
+
description: "The connected store's collections, with the handle `--collection` takes and how many products Shopify counts in each. `--full` adds each one's description, image and SEO.",
|
|
65377
|
+
args: {
|
|
65378
|
+
search: { type: "string", description: "Words in the collection's title or handle", required: false },
|
|
65379
|
+
full: { ...fullArg, description: "Add each collection's description, image and SEO" }
|
|
65380
|
+
}
|
|
65029
65381
|
});
|
|
65030
65382
|
registerSchema({
|
|
65031
65383
|
command: "shopify.stock",
|
|
@@ -65102,7 +65454,7 @@ var productsCommand = defineCommand209({
|
|
|
65102
65454
|
var productCommand = defineCommand209({
|
|
65103
65455
|
meta: {
|
|
65104
65456
|
name: "product",
|
|
65105
|
-
description: "One product: options, prices by country, stock, photo imageIds. Accepts an id, handle or link. Example: baker shopify product the-complete-snowboard"
|
|
65457
|
+
description: "One product: options with buy links, prices by country, stock, photo imageIds, video playbackIds. Accepts an id, handle or link. Example: baker shopify product the-complete-snowboard --content"
|
|
65106
65458
|
},
|
|
65107
65459
|
args: {
|
|
65108
65460
|
ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
@@ -65112,6 +65464,23 @@ var productCommand = defineCommand209({
|
|
|
65112
65464
|
required: false
|
|
65113
65465
|
},
|
|
65114
65466
|
"out-of-stock": { type: "boolean", description: "Only the options out of stock", required: false, default: false },
|
|
65467
|
+
content: {
|
|
65468
|
+
type: "boolean",
|
|
65469
|
+
description: "Its whole details: formatted description, custom fields, subscriptions, translations",
|
|
65470
|
+
required: false,
|
|
65471
|
+
default: false
|
|
65472
|
+
},
|
|
65473
|
+
field: { type: "string", description: "One custom field whole, by its name or key", required: false },
|
|
65474
|
+
variant: {
|
|
65475
|
+
type: "string",
|
|
65476
|
+
description: "With --field: the option whose field it is, by its variantId",
|
|
65477
|
+
required: false
|
|
65478
|
+
},
|
|
65479
|
+
quantity: {
|
|
65480
|
+
type: "string",
|
|
65481
|
+
description: "How many each buy link puts in the cart, 1-99 (default 1)",
|
|
65482
|
+
required: false
|
|
65483
|
+
},
|
|
65115
65484
|
full: { type: "boolean", description: "Everything Baker keeps about it", required: false, default: false }
|
|
65116
65485
|
},
|
|
65117
65486
|
run: async ({ args: args5 }) => {
|
|
@@ -65129,6 +65498,10 @@ var productCommand = defineCommand209({
|
|
|
65129
65498
|
ref: args5.ref,
|
|
65130
65499
|
variantsCursor: args5["variants-cursor"],
|
|
65131
65500
|
outOfStock: args5["out-of-stock"],
|
|
65501
|
+
content: args5.content,
|
|
65502
|
+
field: args5.field,
|
|
65503
|
+
variant: args5.variant,
|
|
65504
|
+
quantity: args5.quantity,
|
|
65132
65505
|
full: args5.full
|
|
65133
65506
|
})
|
|
65134
65507
|
);
|
|
@@ -65144,14 +65517,17 @@ var productCommand = defineCommand209({
|
|
|
65144
65517
|
var collectionsCommand = defineCommand209({
|
|
65145
65518
|
meta: {
|
|
65146
65519
|
name: "collections",
|
|
65147
|
-
description: "The store's collections, with the handles --collection takes. Example: baker shopify collections"
|
|
65520
|
+
description: "The store's collections, with the handles --collection takes. Example: baker shopify collections --full"
|
|
65521
|
+
},
|
|
65522
|
+
args: {
|
|
65523
|
+
search: { type: "string", description: "Words in the title or handle", required: false },
|
|
65524
|
+
full: { type: "boolean", description: "Add description, image and SEO", required: false, default: false }
|
|
65148
65525
|
},
|
|
65149
|
-
args: { search: { type: "string", description: "Words in the title or handle", required: false } },
|
|
65150
65526
|
run: async ({ args: args5 }) => {
|
|
65151
65527
|
try {
|
|
65152
65528
|
const answer = await apiGet(
|
|
65153
65529
|
"/api/shopify/collections",
|
|
65154
|
-
params({ search: args5.search })
|
|
65530
|
+
params({ search: args5.search, full: args5.full })
|
|
65155
65531
|
);
|
|
65156
65532
|
emit2(
|
|
65157
65533
|
answer.collections,
|
|
@@ -65197,13 +65573,20 @@ Examples:
|
|
|
65197
65573
|
baker shopify store # currency, countries, how many products, what is out of stock
|
|
65198
65574
|
baker shopify products --search "snowboard" # what we sell, with price and stock
|
|
65199
65575
|
baker shopify products --stock out_of_stock # --stock in_stock|partly_out|out_of_stock, --collection <handle>
|
|
65200
|
-
baker shopify product the-complete-snowboard # one product: options, prices
|
|
65576
|
+
baker shopify product the-complete-snowboard # one product: options with buyUrl, prices, photos, videos
|
|
65577
|
+
baker shopify product <handle> --content # its whole details: description, custom fields, subscriptions
|
|
65578
|
+
baker shopify product <handle> --field "Size chart" # one custom field, whole
|
|
65579
|
+
baker shopify product <handle> --field custom.fit --variant <variantId> # one option's field, whole
|
|
65580
|
+
baker shopify product <handle> --quantity 2 # buy links that put 2 in the cart
|
|
65201
65581
|
baker shopify product <id> --out-of-stock # only its options that are out of stock
|
|
65202
65582
|
baker shopify stock # every product with options out of stock
|
|
65203
65583
|
baker shopify collections # collections, with handles for --collection
|
|
65204
65584
|
|
|
65205
65585
|
Product photos are already in the image library: place them by imageId
|
|
65206
65586
|
(baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
|
|
65587
|
+
Product videos are in the video library: place them with <Video playbackId="\u2026" />.
|
|
65588
|
+
A page's buy button uses the option's buyUrl, one button per option \u2014 never build a Shopify link
|
|
65589
|
+
yourself, and never use storeUrl (a page to look at) for it.
|
|
65207
65590
|
NO_SHOPIFY_CONNECTION means no store is connected: follow error.fix. SHOPIFY_NOT_AVAILABLE means it cannot be
|
|
65208
65591
|
connected here yet: carry on without it.
|
|
65209
65592
|
Full guide: __tooling__/docs/tools/baker/shopify.md`
|