create-cartbase 0.1.16 → 0.1.17
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/LICENSE +21 -21
- package/README.md +25 -25
- package/dist/index.js +20 -20
- package/package.json +24 -24
- package/template/app/docs/auth.md +105 -105
- package/template/app/docs/carts.md +376 -376
- package/template/app/docs/categories.md +194 -194
- package/template/app/docs/checkout.md +714 -714
- package/template/app/docs/components.md +44 -11
- package/template/app/docs/consent.md +91 -91
- package/template/app/docs/deploy.md +197 -197
- package/template/app/docs/gift-cards.md +153 -153
- package/template/app/docs/metaobjects.md +126 -126
- package/template/app/docs/orders.md +221 -221
- package/template/app/docs/products.md +51 -2
- package/template/app/docs/regions.md +269 -269
- package/template/app/docs/reviews.md +223 -223
- package/template/app/docs/search.md +227 -227
- package/template/app/docs/store.md +47 -47
- package/template/app/docs/subscriptions.md +148 -148
- package/template/app/docs/variables.md +315 -315
- package/template/app/package.json +1 -1
- package/template/app/postcss.config.cjs +11 -11
- package/template/app/src/app/checkout/checkout-page-client.tsx +73 -73
- package/template/app/src/app/checkout/page.tsx +48 -48
- package/template/app/src/app/globals.css +26 -26
- package/template/app/src/app/page.tsx +28 -28
- package/template/app/src/app/products/[handle]/page.tsx +87 -87
- package/template/app/src/app/providers.tsx +64 -64
- package/template/app/src/app/search/page.tsx +23 -23
- package/template/app/src/lib/browser-client.ts +35 -35
- package/template/app/src/lib/config.ts +41 -41
- package/template/app/src/lib/server-client.ts +25 -25
- package/template/app/src/lib/cart-actions.ts +0 -47
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
# Deploy
|
|
2
|
-
|
|
3
|
-
Cartbase builds and hosts your storefront. You send the app's source files
|
|
4
|
-
— one snapshot of the whole project — and Cartbase turns it into a
|
|
5
|
-
running site: first on a **permanent preview URL**, then, when you press
|
|
6
|
-
**Publish** (or call the publish endpoint), on the store's live domain.
|
|
7
|
-
You never touch build servers, DNS, or hosting configuration; the platform
|
|
8
|
-
provisions all of it the moment the store is created, and makes the first
|
|
9
|
-
deploy itself: the store's starting point, a complete storefront on your
|
|
10
|
-
own (still empty) catalog, already running on the preview link. Pull it
|
|
11
|
-
with the CLI, change anything, deploy it back.
|
|
12
|
-
|
|
13
|
-
Two URLs exist per store, both created automatically:
|
|
14
|
-
|
|
15
|
-
| Surface | URL | What serves there |
|
|
16
|
-
|---|---|---|
|
|
17
|
-
| Preview | `https://preview--{store-slug}.cartbase.net` | The newest deploy, always — the link never changes |
|
|
18
|
-
| Live | `https://{store-slug}.cartbase.net` | The last **published** deploy, and nothing else |
|
|
19
|
-
|
|
20
|
-
A failed build never replaces what's live or what's on preview — the
|
|
21
|
-
previous successful deploy keeps serving. Custom domains for the live site
|
|
22
|
-
attach in the admin (Settings → Domains) and don't change anything on this
|
|
23
|
-
page.
|
|
24
|
-
|
|
25
|
-
**These are Admin API endpoints** — they operate the store, so they
|
|
26
|
-
authenticate with an admin session (sign-in cookie), not the storefront
|
|
27
|
-
headers the rest of this corpus uses. In practice you'll deploy through
|
|
28
|
-
the admin UI (**Storefront** in the sidebar), the Cartbase CLI, or an
|
|
29
|
-
agent connection — all three are wrappers over exactly these calls.
|
|
30
|
-
Users who belong to several stores name the target store with an
|
|
31
|
-
`x-cartbase-store: <client-id>` header.
|
|
32
|
-
|
|
33
|
-
## What a deploy is
|
|
34
|
-
|
|
35
|
-
A deploy is a **full snapshot**: the complete file tree of your storefront
|
|
36
|
-
app, uploaded in one request. Snapshot semantics cut both ways — a file
|
|
37
|
-
you stop sending is removed from the deployed site. There is no
|
|
38
|
-
partial-update mode; your working directory is the truth, every time.
|
|
39
|
-
|
|
40
|
-
Bundle rules (enforced server-side; a violation fails the whole deploy
|
|
41
|
-
with a readable `400` before anything is built):
|
|
42
|
-
|
|
43
|
-
- ≤ 1500 files, ≤ 3 MB per file, ≤ 25 MB total (base64-decoded sizes)
|
|
44
|
-
- `package.json` must exist at the bundle root — the app must be a
|
|
45
|
-
buildable Next.js project (`next build`)
|
|
46
|
-
- File content travels base64-encoded (`content_base64`), so binary
|
|
47
|
-
assets — images, fonts — work
|
|
48
|
-
- Never include: `node_modules/` (dependencies install at build time),
|
|
49
|
-
`.next/` (build output is generated), `.git/`, any `.env*` file
|
|
50
|
-
- Not allowed in storefront bundles: `middleware.*`, `app/api/` or
|
|
51
|
-
`pages/api/` routes, `vercel.json` — a storefront is pages plus the
|
|
52
|
-
`@cartbase/storefront` package; server seams and hosting config are
|
|
53
|
-
platform territory. The CORS proxy from the runbook is a
|
|
54
|
-
`next.config` rewrite, which is fine.
|
|
55
|
-
|
|
56
|
-
Error codes you can hit: `empty_bundle`, `bundle_too_large`,
|
|
57
|
-
`invalid_bundle_file`, `invalid_path`, `blocked_path`, `duplicate_path`,
|
|
58
|
-
`invalid_encoding`, `file_too_large`, `missing_package_json`.
|
|
59
|
-
|
|
60
|
-
## Environment — provided, not configured
|
|
61
|
-
|
|
62
|
-
Hosted storefronts receive their store's values automatically at build
|
|
63
|
-
time; do **not** put them in the bundle (`.env*` files are blocked
|
|
64
|
-
anyway):
|
|
65
|
-
|
|
66
|
-
| Variable | Value |
|
|
67
|
-
|---|---|
|
|
68
|
-
| `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` | The store's publishable key, the one input a storefront needs |
|
|
69
|
-
| `NEXT_PUBLIC_CARTBASE_URL` | The platform origin (a constant; injected so a hosted build never guesses) |
|
|
70
|
-
| `NEXT_PUBLIC_CARTBASE_CLIENT_ID` | The store's id, the platform's own door; a storefront never needs to copy it |
|
|
71
|
-
|
|
72
|
-
Only these public values ever reach a storefront build — secret keys are
|
|
73
|
-
never injected, so code that expects one is a design error.
|
|
74
|
-
|
|
75
|
-
## Deploy to preview
|
|
76
|
-
|
|
77
|
-
`POST /api/admin/storefront/deploys` — the one ingestion door. Hosting is
|
|
78
|
-
provisioned when the store is born, so every call is just a deploy; on a
|
|
79
|
-
store whose hosting setup failed, the first call resumes it (a few extra
|
|
80
|
-
seconds).
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
# doc-noexec — admin-session auth; run from an authenticated context.
|
|
84
|
-
curl -s -X POST "$BASE/api/admin/storefront/deploys" \
|
|
85
|
-
-H "content-type: application/json" \
|
|
86
|
-
-d '{
|
|
87
|
-
"files": [
|
|
88
|
-
{ "path": "package.json", "content_base64": "<base64>" },
|
|
89
|
-
{ "path": "app/layout.js", "content_base64": "<base64>" },
|
|
90
|
-
{ "path": "app/page.js", "content_base64": "<base64>" }
|
|
91
|
-
],
|
|
92
|
-
"message": "homepage copy update"
|
|
93
|
-
}'
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Response `201`:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"deploy": {
|
|
101
|
-
"id": "sfd_01J…",
|
|
102
|
-
"target": "preview",
|
|
103
|
-
"status": "building",
|
|
104
|
-
"url": "https://preview--my-store.cartbase.net",
|
|
105
|
-
"message": "homepage copy update",
|
|
106
|
-
"file_count": 3,
|
|
107
|
-
"created_at": "2026-08-03T22:41:00.000Z"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
`message` (≤ 500 chars) is your label in the deploy history — write what
|
|
113
|
-
changed. `source` (`api` | `cli` | `mcp` | `admin` | `agent`) tags where
|
|
114
|
-
the deploy came from; omit it unless you're building tooling.
|
|
115
|
-
|
|
116
|
-
Statuses move `queued → building → ready` (or `failed` / `canceled`).
|
|
117
|
-
Poll the overview endpoint until the deploy you created is `ready`, then
|
|
118
|
-
open the preview URL. Builds of a small app land in roughly a minute;
|
|
119
|
-
`failed` rows carry a readable `error_message`.
|
|
120
|
-
|
|
121
|
-
## Overview — hosting status + deploy history
|
|
122
|
-
|
|
123
|
-
`GET /api/admin/storefront` returns the hosting record and the deploy
|
|
124
|
-
ledger, newest first:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
# doc-noexec — admin-session auth.
|
|
128
|
-
curl -s "$BASE/api/admin/storefront"
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```json
|
|
132
|
-
{
|
|
133
|
-
"storefront": {
|
|
134
|
-
"id": "sfp_01J…",
|
|
135
|
-
"status": "ready",
|
|
136
|
-
"production_url": "https://my-store.cartbase.net",
|
|
137
|
-
"preview_url": "https://preview--my-store.cartbase.net",
|
|
138
|
-
"last_error": null,
|
|
139
|
-
"created_at": "…"
|
|
140
|
-
},
|
|
141
|
-
"deploys": [
|
|
142
|
-
{
|
|
143
|
-
"id": "sfd_01J…",
|
|
144
|
-
"target": "preview",
|
|
145
|
-
"status": "ready",
|
|
146
|
-
"message": "homepage copy update",
|
|
147
|
-
"url": "https://preview--my-store.cartbase.net",
|
|
148
|
-
"source": "cli",
|
|
149
|
-
"file_count": 3,
|
|
150
|
-
"total_bytes": 1930,
|
|
151
|
-
"error_message": null,
|
|
152
|
-
"promoted_deploy_id": null,
|
|
153
|
-
"created_at": "…",
|
|
154
|
-
"updated_at": "…"
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
`storefront` is present from the store's birth (`null` only on a store
|
|
161
|
-
created before hosting-at-birth that has never deployed). `status:
|
|
162
|
-
"provisioning"` / `"failed"` (with `last_error`) describe hosting setup,
|
|
163
|
-
not builds; a failed provisioning resumes automatically on the next
|
|
164
|
-
deploy attempt.
|
|
165
|
-
|
|
166
|
-
## Publish — and rollback
|
|
167
|
-
|
|
168
|
-
`POST /api/admin/storefront/publish` promotes a **ready preview deploy**
|
|
169
|
-
to the live domain. This is the only path to production — nothing
|
|
170
|
-
deploys straight to live, no matter who's asking.
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
# doc-noexec — admin-session auth.
|
|
174
|
-
# Bare POST publishes the newest ready preview:
|
|
175
|
-
curl -s -X POST "$BASE/api/admin/storefront/publish"
|
|
176
|
-
|
|
177
|
-
# Naming an older deploy id IS the rollback mechanism:
|
|
178
|
-
curl -s -X POST "$BASE/api/admin/storefront/publish" \
|
|
179
|
-
-H "content-type: application/json" \
|
|
180
|
-
-d '{ "deploy_id": "sfd_01H…older…" }'
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Response `201` is a new ledger row with `target: "production"` and
|
|
184
|
-
`promoted_deploy_id` pointing at the preview it promoted. Publishing is
|
|
185
|
-
near-instant — the snapshot was already built; the live domain switches
|
|
186
|
-
to it without a rebuild.
|
|
187
|
-
|
|
188
|
-
Rollback is not a separate feature: publish any earlier `ready` deploy
|
|
189
|
-
from the history and the live site is that snapshot again. Roll forward
|
|
190
|
-
the same way.
|
|
191
|
-
|
|
192
|
-
## Workflow summary
|
|
193
|
-
|
|
194
|
-
1. Build locally against your store (the [runbook](BUILD-A-STOREFRONT.md)).
|
|
195
|
-
2. Deploy → check the permanent preview URL.
|
|
196
|
-
3. Iterate — every deploy replaces preview, live is untouched.
|
|
197
|
-
4. Publish when it's right; the deploy history is your undo button.
|
|
1
|
+
# Deploy
|
|
2
|
+
|
|
3
|
+
Cartbase builds and hosts your storefront. You send the app's source files
|
|
4
|
+
— one snapshot of the whole project — and Cartbase turns it into a
|
|
5
|
+
running site: first on a **permanent preview URL**, then, when you press
|
|
6
|
+
**Publish** (or call the publish endpoint), on the store's live domain.
|
|
7
|
+
You never touch build servers, DNS, or hosting configuration; the platform
|
|
8
|
+
provisions all of it the moment the store is created, and makes the first
|
|
9
|
+
deploy itself: the store's starting point, a complete storefront on your
|
|
10
|
+
own (still empty) catalog, already running on the preview link. Pull it
|
|
11
|
+
with the CLI, change anything, deploy it back.
|
|
12
|
+
|
|
13
|
+
Two URLs exist per store, both created automatically:
|
|
14
|
+
|
|
15
|
+
| Surface | URL | What serves there |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Preview | `https://preview--{store-slug}.cartbase.net` | The newest deploy, always — the link never changes |
|
|
18
|
+
| Live | `https://{store-slug}.cartbase.net` | The last **published** deploy, and nothing else |
|
|
19
|
+
|
|
20
|
+
A failed build never replaces what's live or what's on preview — the
|
|
21
|
+
previous successful deploy keeps serving. Custom domains for the live site
|
|
22
|
+
attach in the admin (Settings → Domains) and don't change anything on this
|
|
23
|
+
page.
|
|
24
|
+
|
|
25
|
+
**These are Admin API endpoints** — they operate the store, so they
|
|
26
|
+
authenticate with an admin session (sign-in cookie), not the storefront
|
|
27
|
+
headers the rest of this corpus uses. In practice you'll deploy through
|
|
28
|
+
the admin UI (**Storefront** in the sidebar), the Cartbase CLI, or an
|
|
29
|
+
agent connection — all three are wrappers over exactly these calls.
|
|
30
|
+
Users who belong to several stores name the target store with an
|
|
31
|
+
`x-cartbase-store: <client-id>` header.
|
|
32
|
+
|
|
33
|
+
## What a deploy is
|
|
34
|
+
|
|
35
|
+
A deploy is a **full snapshot**: the complete file tree of your storefront
|
|
36
|
+
app, uploaded in one request. Snapshot semantics cut both ways — a file
|
|
37
|
+
you stop sending is removed from the deployed site. There is no
|
|
38
|
+
partial-update mode; your working directory is the truth, every time.
|
|
39
|
+
|
|
40
|
+
Bundle rules (enforced server-side; a violation fails the whole deploy
|
|
41
|
+
with a readable `400` before anything is built):
|
|
42
|
+
|
|
43
|
+
- ≤ 1500 files, ≤ 3 MB per file, ≤ 25 MB total (base64-decoded sizes)
|
|
44
|
+
- `package.json` must exist at the bundle root — the app must be a
|
|
45
|
+
buildable Next.js project (`next build`)
|
|
46
|
+
- File content travels base64-encoded (`content_base64`), so binary
|
|
47
|
+
assets — images, fonts — work
|
|
48
|
+
- Never include: `node_modules/` (dependencies install at build time),
|
|
49
|
+
`.next/` (build output is generated), `.git/`, any `.env*` file
|
|
50
|
+
- Not allowed in storefront bundles: `middleware.*`, `app/api/` or
|
|
51
|
+
`pages/api/` routes, `vercel.json` — a storefront is pages plus the
|
|
52
|
+
`@cartbase/storefront` package; server seams and hosting config are
|
|
53
|
+
platform territory. The CORS proxy from the runbook is a
|
|
54
|
+
`next.config` rewrite, which is fine.
|
|
55
|
+
|
|
56
|
+
Error codes you can hit: `empty_bundle`, `bundle_too_large`,
|
|
57
|
+
`invalid_bundle_file`, `invalid_path`, `blocked_path`, `duplicate_path`,
|
|
58
|
+
`invalid_encoding`, `file_too_large`, `missing_package_json`.
|
|
59
|
+
|
|
60
|
+
## Environment — provided, not configured
|
|
61
|
+
|
|
62
|
+
Hosted storefronts receive their store's values automatically at build
|
|
63
|
+
time; do **not** put them in the bundle (`.env*` files are blocked
|
|
64
|
+
anyway):
|
|
65
|
+
|
|
66
|
+
| Variable | Value |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` | The store's publishable key, the one input a storefront needs |
|
|
69
|
+
| `NEXT_PUBLIC_CARTBASE_URL` | The platform origin (a constant; injected so a hosted build never guesses) |
|
|
70
|
+
| `NEXT_PUBLIC_CARTBASE_CLIENT_ID` | The store's id, the platform's own door; a storefront never needs to copy it |
|
|
71
|
+
|
|
72
|
+
Only these public values ever reach a storefront build — secret keys are
|
|
73
|
+
never injected, so code that expects one is a design error.
|
|
74
|
+
|
|
75
|
+
## Deploy to preview
|
|
76
|
+
|
|
77
|
+
`POST /api/admin/storefront/deploys` — the one ingestion door. Hosting is
|
|
78
|
+
provisioned when the store is born, so every call is just a deploy; on a
|
|
79
|
+
store whose hosting setup failed, the first call resumes it (a few extra
|
|
80
|
+
seconds).
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# doc-noexec — admin-session auth; run from an authenticated context.
|
|
84
|
+
curl -s -X POST "$BASE/api/admin/storefront/deploys" \
|
|
85
|
+
-H "content-type: application/json" \
|
|
86
|
+
-d '{
|
|
87
|
+
"files": [
|
|
88
|
+
{ "path": "package.json", "content_base64": "<base64>" },
|
|
89
|
+
{ "path": "app/layout.js", "content_base64": "<base64>" },
|
|
90
|
+
{ "path": "app/page.js", "content_base64": "<base64>" }
|
|
91
|
+
],
|
|
92
|
+
"message": "homepage copy update"
|
|
93
|
+
}'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Response `201`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"deploy": {
|
|
101
|
+
"id": "sfd_01J…",
|
|
102
|
+
"target": "preview",
|
|
103
|
+
"status": "building",
|
|
104
|
+
"url": "https://preview--my-store.cartbase.net",
|
|
105
|
+
"message": "homepage copy update",
|
|
106
|
+
"file_count": 3,
|
|
107
|
+
"created_at": "2026-08-03T22:41:00.000Z"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`message` (≤ 500 chars) is your label in the deploy history — write what
|
|
113
|
+
changed. `source` (`api` | `cli` | `mcp` | `admin` | `agent`) tags where
|
|
114
|
+
the deploy came from; omit it unless you're building tooling.
|
|
115
|
+
|
|
116
|
+
Statuses move `queued → building → ready` (or `failed` / `canceled`).
|
|
117
|
+
Poll the overview endpoint until the deploy you created is `ready`, then
|
|
118
|
+
open the preview URL. Builds of a small app land in roughly a minute;
|
|
119
|
+
`failed` rows carry a readable `error_message`.
|
|
120
|
+
|
|
121
|
+
## Overview — hosting status + deploy history
|
|
122
|
+
|
|
123
|
+
`GET /api/admin/storefront` returns the hosting record and the deploy
|
|
124
|
+
ledger, newest first:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# doc-noexec — admin-session auth.
|
|
128
|
+
curl -s "$BASE/api/admin/storefront"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"storefront": {
|
|
134
|
+
"id": "sfp_01J…",
|
|
135
|
+
"status": "ready",
|
|
136
|
+
"production_url": "https://my-store.cartbase.net",
|
|
137
|
+
"preview_url": "https://preview--my-store.cartbase.net",
|
|
138
|
+
"last_error": null,
|
|
139
|
+
"created_at": "…"
|
|
140
|
+
},
|
|
141
|
+
"deploys": [
|
|
142
|
+
{
|
|
143
|
+
"id": "sfd_01J…",
|
|
144
|
+
"target": "preview",
|
|
145
|
+
"status": "ready",
|
|
146
|
+
"message": "homepage copy update",
|
|
147
|
+
"url": "https://preview--my-store.cartbase.net",
|
|
148
|
+
"source": "cli",
|
|
149
|
+
"file_count": 3,
|
|
150
|
+
"total_bytes": 1930,
|
|
151
|
+
"error_message": null,
|
|
152
|
+
"promoted_deploy_id": null,
|
|
153
|
+
"created_at": "…",
|
|
154
|
+
"updated_at": "…"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`storefront` is present from the store's birth (`null` only on a store
|
|
161
|
+
created before hosting-at-birth that has never deployed). `status:
|
|
162
|
+
"provisioning"` / `"failed"` (with `last_error`) describe hosting setup,
|
|
163
|
+
not builds; a failed provisioning resumes automatically on the next
|
|
164
|
+
deploy attempt.
|
|
165
|
+
|
|
166
|
+
## Publish — and rollback
|
|
167
|
+
|
|
168
|
+
`POST /api/admin/storefront/publish` promotes a **ready preview deploy**
|
|
169
|
+
to the live domain. This is the only path to production — nothing
|
|
170
|
+
deploys straight to live, no matter who's asking.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# doc-noexec — admin-session auth.
|
|
174
|
+
# Bare POST publishes the newest ready preview:
|
|
175
|
+
curl -s -X POST "$BASE/api/admin/storefront/publish"
|
|
176
|
+
|
|
177
|
+
# Naming an older deploy id IS the rollback mechanism:
|
|
178
|
+
curl -s -X POST "$BASE/api/admin/storefront/publish" \
|
|
179
|
+
-H "content-type: application/json" \
|
|
180
|
+
-d '{ "deploy_id": "sfd_01H…older…" }'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Response `201` is a new ledger row with `target: "production"` and
|
|
184
|
+
`promoted_deploy_id` pointing at the preview it promoted. Publishing is
|
|
185
|
+
near-instant — the snapshot was already built; the live domain switches
|
|
186
|
+
to it without a rebuild.
|
|
187
|
+
|
|
188
|
+
Rollback is not a separate feature: publish any earlier `ready` deploy
|
|
189
|
+
from the history and the live site is that snapshot again. Roll forward
|
|
190
|
+
the same way.
|
|
191
|
+
|
|
192
|
+
## Workflow summary
|
|
193
|
+
|
|
194
|
+
1. Build locally against your store (the [runbook](BUILD-A-STOREFRONT.md)).
|
|
195
|
+
2. Deploy → check the permanent preview URL.
|
|
196
|
+
3. Iterate — every deploy replaces preview, live is untouched.
|
|
197
|
+
4. Publish when it's right; the deploy history is your undo button.
|