local-storefront 0.2.0 → 0.3.0-rc.2

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/docs/agents.md ADDED
@@ -0,0 +1,366 @@
1
+ # Agents
2
+
3
+ How an AI agent, or any tool, drives local-storefront: the commands to run, what they print, and
4
+ how to read the result without guessing. Every command is non-interactive and exits with a code
5
+ a script can test.
6
+
7
+ ## Before anything
8
+
9
+ - Run `npx local-storefront <command>` in a theme folder. Never `npx lsf`: that is another
10
+ package on npm.
11
+ - Point at the theme with `--theme <dir>` and at the data with `--data <dir>` when the current
12
+ folder is not the theme.
13
+ - Exit codes: `0` nothing to report, `1` something to fix, `2` the command could not run. See
14
+ the [CLI reference](cli.md#exit-codes).
15
+ - Results go to stdout, messages and summaries to stderr.
16
+
17
+ ### What each command prints
18
+
19
+ | Command | For a tool |
20
+ |---|---|
21
+ | `validate`, `check` (`--fix` too), `app check`, `app function run` | `--format json`: one JSON document on stdout. |
22
+ | `app webhook trigger` | `--format json` (`--json` is the same). |
23
+ | `render <path>` | The HTML on stdout. On stderr, each Liquid error, then `status 200 · template product · 0 Liquid errors · 0 warnings`. |
24
+ | `routes` | One path per line. |
25
+ | `fmt --check` | One file to format per line; the summary on stderr. |
26
+ | `profile <path>` | Text. `--json` is a speedscope profile for a flame graph, not a report. |
27
+ | `schema [kind]` | A JSON Schema. |
28
+ | `init`, `app init` | Text, on stderr. They take no `--format`: `init --format json` suggests `--force`, which overwrites the data. Do not follow it. |
29
+ | `app function schema` | On stderr, the file it wrote (`wrote …/schema.graphql: the payment_customization API, 2027-01`). |
30
+ | `serve`, `app dev` | Its lines on stderr; the state as JSON at `GET /__lsf/status`. |
31
+
32
+ ## Tasks
33
+
34
+ ### Set up a theme
35
+
36
+ ```bash
37
+ npm install --save-dev local-storefront
38
+ npx local-storefront init
39
+ ```
40
+
41
+ `init` writes `shopify-local/` with a demo store, and prints on stderr
42
+ `./shopify-local: wrote 16 files, kept 0 existing ones, refreshed schema/`. It keeps the
43
+ files that exist, so it is safe to run again (`wrote 0 files, kept 16 existing ones`).
44
+ `--force` overwrites them. Exit `0`.
45
+
46
+ To use your own products instead of the demo's, delete `shopify-local/products/*.json`,
47
+ `customers.json` and `gift_cards.json`, write yours (next task), then run
48
+ `npx local-storefront validate --format json`. The demo's collections, menus and subscriptions
49
+ still name its products: each one is an `unknown_product` or an `unknown_reference`, at its
50
+ `file` (`collections.json`, `menus.json`, `store.json`) and `path`. Replace or remove each.
51
+ Remove from the last `path` to the first: removing an item of a list moves the `path` of the
52
+ items after it. A collection or a selling-plan group may be left with no products
53
+ (`"products": []`).
54
+
55
+ The demo's Apparel, Home goods, Accessories and Sale collections pick products by `type`
56
+ (`Apparel`, `Home`, `Accessories`) and by the tag `sale`: give yours a `type` and `tags`, or
57
+ change the `rules` of each collection. Otherwise they are empty, and every check still passes.
58
+ The shop's name is `shop.name` in `store.json` (`Local Supply Co.`), and the pages and articles
59
+ of `pages.json` and `blogs.json` are the demo's too.
60
+
61
+ ### Write store data
62
+
63
+ One file per product, `shopify-local/products/trail-runner.json`:
64
+
65
+ ```json
66
+ {
67
+ "title": "Trail Runner",
68
+ "price": "129.00",
69
+ "compare_at_price": "149.00",
70
+ "options": ["Size"],
71
+ "variants": [
72
+ { "options": ["42"], "sku": "TRAIL-42", "inventory_quantity": 5 },
73
+ { "options": ["43"], "sku": "TRAIL-43", "inventory_quantity": 0 }
74
+ ],
75
+ "images": ["products/trail-runner.jpg"]
76
+ }
77
+ ```
78
+
79
+ The customers, `shopify-local/customers.json`:
80
+
81
+ ```json
82
+ {
83
+ "customers": [{
84
+ "email": "alex.martin@example.com",
85
+ "first_name": "Alex",
86
+ "addresses": [{ "address1": "1 Main St", "city": "New York", "country_code": "US", "zip": "10001" }],
87
+ "orders": [{ "line_items": [{ "variant": "TRAIL-42", "quantity": 1 }] }]
88
+ }]
89
+ }
90
+ ```
91
+
92
+ Money is a decimal string (`"129.00"`) or cents (`12900`), never a JSON float. The handle comes
93
+ from the file name (`trail-runner`). An image that is not in `files/` renders as a placeholder
94
+ of its size: test data needs no image files. The rest, in the data guide:
95
+ [conventions](data-format.md#conventions), [products](data-format.md#products),
96
+ [customers](data-format.md#customers-and-sessions), [images](data-format.md#images-and-files),
97
+ [error codes](data-format.md#error-codes).
98
+
99
+ ### Check the store data
100
+
101
+ ```bash
102
+ npx local-storefront validate --format json
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "ok": false,
108
+ "errors": 1,
109
+ "warnings": 0,
110
+ "data": "./shopify-local",
111
+ "diagnostics": [
112
+ {
113
+ "severity": "error",
114
+ "code": "unknown_field",
115
+ "file": "products/shirt.json",
116
+ "path": "/titel",
117
+ "message": "unknown field \"titel\"",
118
+ "hint": "did you mean \"title\"?"
119
+ }
120
+ ],
121
+ "app": null
122
+ }
123
+ ```
124
+
125
+ `path` is a JSON Pointer into `file`, in the folder `data`. Apply the `hint` when there is one,
126
+ then run the command again until `ok` is `true`. `app` is `null` outside a Shopify app. The
127
+ codes: [data guide](data-format.md#error-codes). The format of every field:
128
+ `npx local-storefront docs`, or `npx local-storefront schema <kind>` for a JSON Schema.
129
+
130
+ Fix the errors in `products/` and `customers.json` first. A product or a customer with a value
131
+ that cannot be read (a `wrong_type`, an `invalid_value`, an `invalid_format`, a
132
+ `missing_field`) is left out, so each reference to it is an error too, in its own file: in the
133
+ demo store, one float price gives 4 errors in 4 files. Run `validate` again before you edit a
134
+ reference.
135
+
136
+ ### Check the theme
137
+
138
+ ```bash
139
+ npx local-storefront check --format json
140
+ ```
141
+
142
+ ```json
143
+ {
144
+ "ok": false,
145
+ "files": 191,
146
+ "problems": 1,
147
+ "fixable": 1,
148
+ "fixed": 0,
149
+ "changes_output": 1,
150
+ "diagnostics": [
151
+ {
152
+ "severity": "error",
153
+ "code": "strict_liquid_rejected",
154
+ "file": "snippets/card.liquid",
155
+ "line": 1,
156
+ "message": "Shopify's strict Liquid parser rejects this {% render %}: Expected end_of_string but found pipe",
157
+ "subject": "{% render %}",
158
+ "markup": "'image', alt: image.alt | default: shop.name",
159
+ "fix": {
160
+ "written": false,
161
+ "markup": "'image', alt: image_alt",
162
+ "before": ["assign image_alt = image.alt | default: shop.name"],
163
+ "changes_output": "`alt` gets `image.alt | default: shop.name`"
164
+ }
165
+ }
166
+ ]
167
+ }
168
+ ```
169
+
170
+ This is the dry run: each `fix` is exactly what `--fix` writes. A diagnostic with `no_fix`, or
171
+ with neither key (an `unknown_filter`, which may have a `hint`), is not fixed by `--fix`: see
172
+ the rules of the next task.
173
+ Exit `1` while a problem is left. Every key and code: [CLI reference](cli.md#the-json-format).
174
+
175
+ `check` reads the files and renders none: a Liquid error at render time (a division by zero)
176
+ shows only with `render --strict`.
177
+
178
+ ### Fix what Shopify's strict Liquid parser rejects
179
+
180
+ ```bash
181
+ npx local-storefront check --fix --format json
182
+ ```
183
+
184
+ Rules for an agent:
185
+
186
+ - Run it on a clean working tree, so that `git diff` shows exactly what it wrote.
187
+ - The fixed problems stay in `diagnostics`, with `fix.written: true`; their `severity` is still
188
+ `error`. `problems` counts what is left: the errors without `fix.written: true` (a `warning`
189
+ never counts).
190
+ - A fix with `changes_output` changes the rendered HTML on purpose: the code now does what it
191
+ says. Report each one to the person, with its `file` and `line`. Do not revert it to silence
192
+ the change.
193
+ - Two `no_fix` reasons have a rewrite in [strict Liquid](strict-liquid.md#fix-by-hand):
194
+ `` `a.b` is not a name the strict parser reads `` and `` `=<` is read as an operator… ``.
195
+ Apply the rewrite, and report it as you report a `changes_output`: the page changes. Any other
196
+ `no_fix` needs a person's decision. Do not invent a rewrite: report it with its `file`,
197
+ `line` and `no_fix`.
198
+ - An `unknown_filter` with a `hint` is a typo (`uppercas`, `did you mean "upcase"?`): apply
199
+ the hint, and report it, since the page changes. One without a `hint` may be a filter that
200
+ local-storefront lacks: report it.
201
+ - It is safe to run twice: the second run writes nothing.
202
+ - Run `npx local-storefront check --format json` again: `problems` is `0`, or every problem left
203
+ is reported to the person.
204
+
205
+ Guide: [strict Liquid](strict-liquid.md).
206
+
207
+ ### Render a page
208
+
209
+ ```bash
210
+ npx local-storefront render /products/trail-runner --strict > page.html
211
+ ```
212
+
213
+ The HTML goes to stdout, and the errors are in it too, as `Liquid error (sections/x line 12): …`.
214
+ On stderr, each error, then a summary:
215
+
216
+ ```text
217
+ Liquid error (layout/theme line 34): divided by 0
218
+ status 200 · template product · 1 Liquid error · 0 warnings
219
+ ```
220
+
221
+ With `--strict`, exit `1` when the page has Liquid errors, or when nothing is at the path
222
+ (`error: nothing at /x: the page is the theme's 404`). `npx local-storefront routes` lists every
223
+ page of the data, and each one renders: loop over it to check them all (`--all` adds the other
224
+ languages, a search and the sections the theme fetches). It leaves out `/account`, a page of
225
+ lsf's, not of the theme.
226
+
227
+ ### Run the checks in CI
228
+
229
+ ```yaml
230
+ # .github/workflows/storefront.yml
231
+ name: storefront
232
+ on: [push, pull_request]
233
+ jobs:
234
+ storefront:
235
+ runs-on: ubuntu-latest
236
+ steps:
237
+ - uses: actions/checkout@v7
238
+ - uses: actions/setup-node@v7
239
+ with:
240
+ node-version: 24 # 18 or later
241
+ cache: npm
242
+ - run: npm ci
243
+ - run: npx local-storefront validate --format github
244
+ - run: npx local-storefront check --format github
245
+ - name: Render every page, and fail on a Liquid error
246
+ run: |
247
+ npx local-storefront routes --all > routes.txt
248
+ failed=0
249
+ while read -r route; do
250
+ npx local-storefront render "$route" --strict --format github > /dev/null || failed=1
251
+ done < routes.txt
252
+ exit $failed
253
+ ```
254
+
255
+ `npm ci` needs `package-lock.json`: commit it with `package.json` after the
256
+ `npm install --save-dev local-storefront` of the setup. Commit `shopify-local/` too, `schema/`
257
+ and `README.md` included: the steps read the store data, and editors read `schema/`, which
258
+ `init` writes again at each run. `--format github` shows each problem on its line of the pull
259
+ request. `routes --all` adds the other languages, a search and the sections the theme fetches;
260
+ the step stops at once when `routes` fails (a wrong `--theme`), renders every route, and fails
261
+ at the end if one has a Liquid error. Add
262
+ `- run: npx local-storefront fmt --check` only once the theme is formatted: a stock theme is not.
263
+ With browser tests: [testing](testing.md#in-ci).
264
+
265
+ ### Format files
266
+
267
+ ```bash
268
+ npx local-storefront fmt --check
269
+ ```
270
+
271
+ Lists the files that are not formatted; exit `1` if there are any. `npx local-storefront fmt`
272
+ formats them. `npx local-storefront fmt <files>` formats only those.
273
+
274
+ ### Find what makes a page slow
275
+
276
+ ```bash
277
+ npx local-storefront profile /collections/all --points
278
+ ```
279
+
280
+ Points are deterministic: the same at every run and on every machine, so they can be compared
281
+ before and after a change. Times are not. Guide: [performance](performance.md).
282
+
283
+ ### Run the Functions of a Shopify app
284
+
285
+ ```bash
286
+ npx local-storefront app function run --app ../my-app --scenario <name> --format json
287
+ ```
288
+
289
+ Prints the checkout the Functions produce for the cart of a scenario, a file of
290
+ `shopify-local/checkouts/` (`app init` writes a set). Without `--scenario`, it is the cart of the
291
+ store data's `session`, empty in the demo store. The JSON has `lines`,
292
+ `discounts`, `deliveryGroups`, `paymentMethods` and `expressMethods` (with their `placements`),
293
+ `errors`, `cost`, in `runs` the input, the output, the logs and the error of each Function, and in
294
+ `idle` the Functions the shop's checkout left out for want of an enabled owner (a payment
295
+ customization, …), with why. A Function named on the command line runs whether or not the shop
296
+ has an owner of it. Exits with `1` when a Function fails to run, or when a Function named on the
297
+ command line did not run; it says why. Guide: [checkout](checkout.md#owners-and-scenarios).
298
+
299
+ `npx local-storefront app function schema <handle>` writes the Function's `schema.graphql`, from
300
+ lsf's copy of the API's schema, offline.
301
+
302
+ For CI, `npx local-storefront app check --build --format json` builds the extensions, then runs
303
+ the Functions of every scenario and checks the `expect` of each, what its checkout must look like
304
+ ([expected results](checkout.md#expected-results)): `ok`, `failures`, `invalid`, `expectations`
305
+ (`checked`, and in `unmet` the `scenario`, the `assertion`, `expected` and `actual` of each
306
+ expectation not met), `notOffered` (an extension the checkout does not offer, and why),
307
+ `stale` (a Function whose files changed after its module was built), `themeExtensions` (what
308
+ Shopify rejects in a theme app extension), each a failure, and `ranInNoScenario`, the Functions
309
+ no scenario ran (a warning: the exit code stays `0`). Assert what a Function decides with an `expect` rather than piping
310
+ `app function run --format json` into `jq`.
311
+
312
+ ### Test the backend of an app
313
+
314
+ ```bash
315
+ npx local-storefront app dev --app . --app-url http://localhost:3000 --port 9393 --static --quiet
316
+ ```
317
+
318
+ The local Admin API, OAuth and the app's webhooks, for its backend, with the app's checkout.
319
+ `serve --app . --only admin` runs the Admin API alone ([what runs](cli.md#what-runs)): a test
320
+ that needs neither the theme nor the checkout. Once `ready:` is printed,
321
+ `GET /__lsf/admin` gives the shop (`defaultShop`), the path of the API (`graphql`) and the token
322
+ (`shops[0].token`). Webhooks go to `--app-url`, signed with the client secret the start lines
323
+ name; without it, those that lead off this machine are `not_sent`.
324
+ `GET /__lsf/webhooks?shop=<shop>&since=<last>&wait=1` waits for one: `last` is in every answer
325
+ of `GET /__lsf/webhooks`, read it before the action. A shop for each test, orders from the
326
+ checkout, resets: [test your backend](admin.md#test-your-backend).
327
+
328
+ ### Run a server
329
+
330
+ ```bash
331
+ npx local-storefront serve --port 0 --static --quiet
332
+ ```
333
+
334
+ - **Ready.** Run it in the background and read its stderr until a line matches
335
+ `^ready:\s+(http://\S+)`: the URL is group 1. Every line of the server goes to stderr.
336
+ `serving:` names the parts it runs (`theme`, `checkout`, `admin`: [what runs](cli.md#what-runs)).
337
+ With `--app`, the `admin:` lines (the token, the client secret), `webhooks:`, `proxy:`,
338
+ `embedded:` (the app in lsf's admin), `pricing:`, `partner:` and `polaris:` come before
339
+ `ready:`, each when it applies, `home:` and `watch:` after it; read the token from
340
+ `GET /__lsf/admin` rather than from the text. Without `--app`, or with `--only` leaving out
341
+ the admin part, none of them is printed, and the Admin API is not served: its routes answer
342
+ `404` with an `X-LSF-Reason`.
343
+ - **Failed.** It exits with `2` before `ready:` when the port is taken (`error: cannot listen
344
+ on …`), or with `--strict` when the data has errors (on stderr; `validate --format json` gives
345
+ them as JSON).
346
+ - **Stop.** Send `SIGTERM`: it finishes the requests it is answering, for 3 seconds at most,
347
+ and exits with `0`.
348
+
349
+ While it runs:
350
+
351
+ - `GET /__lsf/status` gives the data diagnostics, the templates, the customers and every page.
352
+ - `PUT /__lsf/session` sets the cart, the customer and data for one session; a wrong body
353
+ answers `422` with the same diagnostics as `npx local-storefront validate --format json`.
354
+ - The header `x-lsf-liquid-errors` counts the Liquid errors of a page; it is absent when there
355
+ are none. `x-lsf-not-found` says why a page is a 404.
356
+
357
+ Reference: [control API](control-api.md).
358
+
359
+ ## What not to expect
360
+
361
+ local-storefront renders themes, and runs your own app with `--app <dir>`: its checkout, its
362
+ Functions, its UI extensions, the Admin API and webhooks for its backend
363
+ ([checkout](checkout.md), [Admin API](admin.md)). It runs no third-party app in the theme, and
364
+ the theme's cart has no discounts, taxes or shipping rates. A difference with Shopify that is
365
+ known is listed in [compatibility](compatibility.md#known-differences): check there before you
366
+ report one.