@commercelayer/agent-cli 0.2.2 → 0.2.5

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/dist/store.js CHANGED
@@ -8,7 +8,6 @@ import YAML from 'yaml';
8
8
  import { fail } from './output.js';
9
9
  import { paths } from './files.js';
10
10
  import { productFiles, readProductFile } from './products.js';
11
- import { checkPromotions } from './promotions.js';
12
11
  /** The keys a product may have. Stock is not one: inventory lives in Commerce Layer. */
13
12
  export const PRODUCT_KEYS = ['sku', 'name', 'description', 'image_url', 'category', 'prices'];
14
13
  export const THEME_KEYS = ['color', 'logo', 'hero', 'tagline'];
@@ -62,18 +61,6 @@ theme:
62
61
  # hero: img/hero.jpg # the home page's wide image; without it a band in the accent
63
62
  # tagline: Small-batch coffee, roasted in Milan # at most 120 characters
64
63
 
65
- promotions:
66
- # Discount codes and automatic discounts: rules (when) and actions (what).
67
- # The id is unique and stable. Amounts are in the market currency, like prices.
68
- # - id: welcome10
69
- # name: Welcome, 10% off
70
- # rules: [{ coupon: WELCOME10 }] # codes have 8 to 40 characters
71
- # actions: [{ percentage: 10 }]
72
- # - id: free-shipping-50
73
- # market: eu
74
- # rules: [{ order_amount: { min: 50 } }]
75
- # actions: [free_shipping]
76
-
77
64
  storefront:
78
65
  # Only for a custom storefront (your own web app in this repo, published with
79
66
  # c11r storefront publish): it replaces the theme. Its folder, from the repo root.
@@ -149,6 +136,8 @@ export function loadStore() {
149
136
  }
150
137
  }
151
138
  }
139
+ if (doc.promotions !== undefined)
140
+ fail('invalid_store', 'store.yml has promotions:, which is not part of the store files.', 'Promotions are live settings in Commerce Layer: create each one with `c11r promotions create -f <file>` (the same entry as YAML or JSON), then remove promotions: from store.yml.');
152
141
  const policies = (doc.policies ?? {});
153
142
  for (const [key, url] of Object.entries(policies)) {
154
143
  if (url && !/^https?:\/\//.test(String(url)))
@@ -157,8 +146,7 @@ export function loadStore() {
157
146
  const theme = checkTheme(doc.theme);
158
147
  const currencies = [...new Set(markets.map((m) => m.currency))];
159
148
  const products = checkProducts(sourced, currencies);
160
- const promotions = checkPromotions(doc.promotions, markets, products);
161
- return { name: doc.name, markets, products, storefront: doc.storefront, policies, theme, promotions };
149
+ return { name: doc.name, markets, products, storefront: doc.storefront, policies, theme };
162
150
  }
163
151
  /** The product rules, shared by loadStore and catalog pull: errors name each product's source. */
164
152
  export function checkProducts(sourced, currencies) {
@@ -270,19 +258,5 @@ export function diff(state, desired) {
270
258
  if (!(id in after[kind]))
271
259
  changes.push({ action: 'delete', type, id });
272
260
  }
273
- // Promotions, by id. An API that does not know them (undefined) is skipped: apply refuses instead.
274
- if (!applied || state.promotions !== undefined) {
275
- const before = (applied ? state.promotions : undefined) ?? {};
276
- const want = Object.fromEntries((desired.promotions ?? []).map((p) => [p.id, p]));
277
- for (const id of Object.keys(want)) {
278
- if (!(id in before))
279
- changes.push({ action: 'create', type: 'promotion', id });
280
- else if (canonical(before[id]) !== canonical(want[id]))
281
- changes.push({ action: 'update', type: 'promotion', id });
282
- }
283
- for (const id of Object.keys(before))
284
- if (!(id in want))
285
- changes.push({ action: 'delete', type: 'promotion', id });
286
- }
287
261
  return changes;
288
262
  }
@@ -3,10 +3,13 @@
3
3
  // top-level category), styles.css (theme tokens first, then the drop-in.css
4
4
  // overrides, then the page) and app.js (market switch, category links, and a
5
5
  // product page at #/p/<sku>). Product images are the store's, at /img/<file> (from
6
- // .commercelayer/img/, uploaded by apply). The agent then makes it the human's.
6
+ // .commercelayer/img/, uploaded by apply); a product without one shows the tile the
7
+ // store host draws at /tiles/<sku>.svg. The agent then makes it the human's.
7
8
  import { isLocalImage } from './store.js';
8
9
  /** img/<file> is the store's image at /img/<file> on the store host, whatever page links it. */
9
10
  const imageSrc = (url) => (isLocalImage(url) ? `/${url}` : url);
11
+ /** A product without an image gets the tile the store host draws for it, at /tiles/<sku>.svg. */
12
+ const productImage = (p) => (p.image_url ? imageSrc(p.image_url) : `/tiles/${encodeURIComponent(p.sku)}.svg`);
10
13
  const esc = (t) => String(t ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
11
14
  /** The scaffold's files, by path relative to the storefront folder. */
12
15
  export function storefrontFiles(store, clientId) {
@@ -17,7 +20,7 @@ function indexHtml(store, clientId) {
17
20
  const card = (p, h) => {
18
21
  const href = `#/p/${encodeURIComponent(p.sku)}`;
19
22
  return ` <article class="product" id="p-${esc(p.sku)}">
20
- <a class="media" href="${esc(href)}" tabindex="-1">${p.image_url ? `<img src="${esc(imageSrc(p.image_url))}" alt="${esc(p.name)}">` : '<div class="placeholder" aria-hidden="true"></div>'}</a>
23
+ <a class="media" href="${esc(href)}" tabindex="-1"><img src="${esc(productImage(p))}" alt="${esc(p.name)}"${p.image_url ? '' : ' loading="lazy"'}></a>
21
24
  <${h}><a href="${esc(href)}">${esc(p.name)}</a></${h}>
22
25
  ${p.description ? ` <p>${esc(p.description)}</p>\n` : ''} <cl-price code="${esc(p.sku)}"><cl-price-amount type="compare-at"></cl-price-amount> <cl-price-amount type="price"></cl-price-amount></cl-price>
23
26
  <cl-availability code="${esc(p.sku)}"><cl-availability-status type="available">In stock</cl-availability-status><cl-availability-status type="unavailable">Sold out</cl-availability-status></cl-availability>
@@ -53,7 +56,7 @@ ${p.description ? ` <p>${esc(p.description)}</p>\n` : ''} <cl-pric
53
56
  }
54
57
  const options = store.markets.map((m) => ` <option value="${esc(m.code)}">${esc(m.name)} (${esc(m.currency)})</option>`).join('\n');
55
58
  // What the product page shows, by sku. Prices and availability come live from drop-in.js.
56
- const data = Object.fromEntries(store.products.map((p) => [p.sku, { name: p.name, description: p.description, image_url: p.image_url && imageSrc(p.image_url), category: p.category }]));
59
+ const data = Object.fromEntries(store.products.map((p) => [p.sku, { name: p.name, description: p.description, image_url: productImage(p), category: p.category }]));
57
60
  const json = JSON.stringify(data, null, 2).replace(/</g, '\\u003c');
58
61
  return `<!doctype html>
59
62
  <html lang="en">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercelayer/agent-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
4
4
  "description": "c11r: the Commerce Layer CLI for coding agents. Builds a sandbox store that people and shopping agents can buy from.",
5
5
  "homepage": "https://commercelayer.ai",
6
6
  "keywords": [