@thorprovider/storefront 0.3.1
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/CHANGELOG.md +61 -0
- package/README.md +333 -0
- package/assets/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/assets/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/assets/templates/app/app/globals.css +101 -0
- package/assets/templates/app/app/layout.tsx +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +88 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +42 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +47 -0
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +98 -0
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +17 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +48 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/index.ts +7 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +33 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +28 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +46 -0
- package/assets/templates/views/home/index.tsx +142 -0
- package/assets/templates/views/home/registry.tsx +65 -0
- package/bin/storefront.js +531 -0
- package/commands/sf-add-view.md +26 -0
- package/commands/sf-theme.md +20 -0
- package/commands/sf-view.md +25 -0
- package/package.json +48 -0
- package/recipes/archetype.schema.json +39 -0
- package/recipes/archetypes.json +148 -0
- package/recipes/recipe.schema.json +59 -0
- package/recipes/recipes.json +90 -0
- package/recipes/sections.json +46 -0
- package/recipes/validate.mjs +190 -0
- package/scripts/render-templates.mjs +255 -0
- package/scripts/sync-assets.mjs +87 -0
- package/skills/building-storefronts/SKILL.md +178 -0
- package/skills/building-storefronts/references/frontend-integration.md +229 -0
- package/skills/json-render-core/SKILL.md +291 -0
- package/skills/json-render-next/SKILL.md +194 -0
- package/skills/json-render-react/SKILL.md +298 -0
- package/skills/json-render-remotion/SKILL.md +111 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/json-render-solid/SKILL.md +204 -0
- package/skills/nextjs-shadcn/SKILL.md +367 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +162 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +261 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-theme-gen/SKILL.md +61 -0
- package/skills/sf-view-gen/SKILL.md +116 -0
- package/skills/sf-view-gen/assets/recipes/archetypes.json +148 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +90 -0
- package/skills/sf-view-gen/assets/recipes/sections.json +46 -0
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +86 -0
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +48 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +192 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +45 -0
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +122 -0
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +49 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +168 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +49 -0
- package/skills/sf-view-gen/references/views.md +69 -0
- package/skills/sf-view-gen/scripts/validate.mjs +190 -0
- package/skills/shadcn-component-discovery/SKILL.md +273 -0
- package/skills/shadcn-component-discovery/references/registries.md +226 -0
- package/skills/shadcn-theming/SKILL.md +104 -0
- package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
- package/skills/shadcn-theming/references/theming-guide.md +90 -0
- package/skills/storefront-best-practices/SKILL.md +421 -0
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
- package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
- package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
- package/skills/storefront-best-practices/reference/components/footer.md +112 -0
- package/skills/storefront-best-practices/reference/components/hero.md +241 -0
- package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
- package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
- package/skills/storefront-best-practices/reference/components/popups.md +221 -0
- package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
- package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
- package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
- package/skills/storefront-best-practices/reference/components/search.md +101 -0
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
- package/skills/storefront-best-practices/reference/design.md +388 -0
- package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
- package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
- package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
- package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
- package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
- package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
- package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
- package/skills/storefront-best-practices/reference/medusa.md +307 -0
- package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
- package/skills/storefront-best-practices/reference/seo.md +195 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- **Renamed package**: `@thorprovider/create-storefront` → `@thorprovider/storefront`;
|
|
6
|
+
the `bin` is now `storefront` (`npx @thorprovider/storefront create`).
|
|
7
|
+
- **Single scaffold command**: `storefront create <dir>` scaffolds the storefront and
|
|
8
|
+
installs the OpenCode command suite in the same run. The standalone `install` action
|
|
9
|
+
is gone; `storefront update` refreshes, prunes removed files, and migrates the
|
|
10
|
+
version record.
|
|
11
|
+
- **Interactive wizard**: on a TTY, `create` prompts for name, site name, backend URL,
|
|
12
|
+
and preset (Maia preselected). Brand color is decided via `/sf-theme`, not the wizard.
|
|
13
|
+
Without a TTY, every required input must be passed as a flag or the command fails
|
|
14
|
+
fast before writing anything.
|
|
15
|
+
- **Command surface**: `<dir>` is positional; `--dir` is removed; `--name` is a flag or
|
|
16
|
+
wizard prompt. Commands are `storefront create|update|doctor <dir>`.
|
|
17
|
+
- **Removed `/sf-init`**: the `sf-init` command and `sf-scaffold` skill are gone.
|
|
18
|
+
Scaffolding is fully deterministic inside the binary.
|
|
19
|
+
- **Decoupled templates**: the scaffold templates moved from the `sf-scaffold` skill
|
|
20
|
+
into `assets/templates/` at the package root.
|
|
21
|
+
- **Version record**: `.opencode/storefront.json` (replacing `.create-storefront.json`);
|
|
22
|
+
`update` migrates existing installs.
|
|
23
|
+
|
|
24
|
+
## 0.3.0
|
|
25
|
+
|
|
26
|
+
- **Embedded assets**: each skill now carries the templates, recipes, and scripts it
|
|
27
|
+
uses inside its own directory (`assets/`, `scripts/`, `references/`) and addresses
|
|
28
|
+
them by relative path. No path marker is needed to locate the package.
|
|
29
|
+
- **Versioned install command**: `bin` exposes `create-storefront` with `install`,
|
|
30
|
+
`update`, and `doctor`. Installation is an explicit command; there is no
|
|
31
|
+
`postinstall`. The installer records `{ version, installedAt }` in
|
|
32
|
+
`.opencode/.create-storefront.json` (replacing the `.create-storefront-root` marker).
|
|
33
|
+
- **Single source, generated copies**: `recipes/` stays the single source at the
|
|
34
|
+
package root; `scripts/sync-assets.mjs` generates the per-skill copies and a
|
|
35
|
+
`--check` mode fails on drift. The check runs at build/pretest and before the
|
|
36
|
+
installer copies anything into a target project.
|
|
37
|
+
- **Positive source-of-truth rule**: the skills state that the workflow uses the assets
|
|
38
|
+
inside the skill and that an undecided choice is resolved by asking the user.
|
|
39
|
+
- **Named interfaces**: each skill declares the target-project inputs it reads and the
|
|
40
|
+
outputs it produces.
|
|
41
|
+
- **General self-containment check**: the test fails on any absolute machine path in a
|
|
42
|
+
shipped artifact, independent of any name.
|
|
43
|
+
|
|
44
|
+
## 0.2.0
|
|
45
|
+
|
|
46
|
+
- Per-view templates: every recipe view (`home`, `product`, `category`, `checkout`,
|
|
47
|
+
`user-panel`) ships as a self-contained module under `templates/views/<view>/`.
|
|
48
|
+
- Generated storefronts now compose views through a barrel (`lib/<name>/views/index.ts`);
|
|
49
|
+
shared files (`catalog.ts`, `registry.tsx`, `runtime.ts`, `spec/index.ts`) derive from
|
|
50
|
+
the registered views and are never edited per view.
|
|
51
|
+
- `/sf-add-view` copies a view module and registers one barrel entry, substituting only
|
|
52
|
+
the imported block identifier for the resolved variant. No hand-authored wiring.
|
|
53
|
+
- Optional sections (testimonials, FAQ, statistics, logo cloud, pricing) ship as
|
|
54
|
+
fragments under `views/sections/` and are validated against `recipes/sections.json`.
|
|
55
|
+
- `sf-scaffold` copies `templates/app/` plus `templates/views/home/` and registers
|
|
56
|
+
`home` in the barrel.
|
|
57
|
+
- Templates and skills no longer reference any repository-local application.
|
|
58
|
+
|
|
59
|
+
## 0.1.3
|
|
60
|
+
|
|
61
|
+
- Previous release.
|
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@thorprovider/storefront"
|
|
3
|
+
purpose: "Deterministic storefront scaffolder and installable command suite for Thor Commerce"
|
|
4
|
+
lastReviewed: "2026-10-01"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @thorprovider/storefront
|
|
8
|
+
|
|
9
|
+
Deterministic storefront scaffolder and installable command suite for Thor Commerce.
|
|
10
|
+
|
|
11
|
+
`npx @thorprovider/storefront create <dir>` scaffolds a complete, buildable storefront
|
|
12
|
+
and installs the `/sf-*` OpenCode command suite into `<dir>/.opencode/` in the same
|
|
13
|
+
run. It ships static project templates, a recipe registry, and vendored skills, and
|
|
14
|
+
installs them into any project's `.opencode/` directory. Nothing else from the Thor
|
|
15
|
+
Commerce monorepo is required at generation time.
|
|
16
|
+
|
|
17
|
+
This README is organised in two parts:
|
|
18
|
+
|
|
19
|
+
- **Getting started** — for consumers who install the published package and want a
|
|
20
|
+
running storefront. Start here.
|
|
21
|
+
- **Contributing** — for maintainers working on the package itself. See
|
|
22
|
+
[Development](#development).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
You need no checkout of the Thor Commerce monorepo. The package is on npm and the
|
|
29
|
+
storefront it generates resolves its own dependencies from the registry.
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
|
|
33
|
+
| Requirement | Supported |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Node.js | >= 18.17.0 |
|
|
36
|
+
| Package manager | Yarn with the `node-modules` linker (for generated projects) |
|
|
37
|
+
| OpenCode | a version that supports project skills in `.opencode/skills/` |
|
|
38
|
+
| OS | macOS, Linux, Windows (via WSL) |
|
|
39
|
+
| Network | access to the npm registry (the generated project installs its dependencies from it) |
|
|
40
|
+
|
|
41
|
+
Generated projects use Yarn with the `node-modules` linker, not Plug'n'Play (PnP).
|
|
42
|
+
Yarn 4 defaults to PnP, which Next.js 16's Turbopack cannot resolve from, so the
|
|
43
|
+
scaffold writes a `.yarnrc.yml` setting `nodeLinker: node-modules` before installing
|
|
44
|
+
dependencies. PnP is not used. You do not need to configure this yourself.
|
|
45
|
+
|
|
46
|
+
### Install
|
|
47
|
+
|
|
48
|
+
Run the scaffolder directly from npm — no global install required. `npx` and
|
|
49
|
+
`yarn dlx` are equivalent:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @thorprovider/storefront create my-shop \
|
|
53
|
+
--site-name "My Shop" \
|
|
54
|
+
--backend-url http://localhost:9000 \
|
|
55
|
+
--preset maia
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn dlx @thorprovider/storefront create my-shop --preset maia
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The first positional argument is the target directory: it is created if missing, and
|
|
63
|
+
it is where `.opencode/`, the Next.js app, and `lib/<name>/` live. Run the command
|
|
64
|
+
from the parent directory you want the project created in.
|
|
65
|
+
|
|
66
|
+
In a terminal, `create` runs an interactive wizard for any input you omit (name,
|
|
67
|
+
site name, backend URL, preset — Maia is preselected). Without a terminal, every
|
|
68
|
+
required input must be passed as a flag; the command fails fast and writes nothing
|
|
69
|
+
rather than guessing. Brand color is never asked by the wizard — apply it later with
|
|
70
|
+
`/sf-theme`, or pass `--brand` on the command line.
|
|
71
|
+
|
|
72
|
+
Once the package is installed as a dependency, the shorter binary form is available:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
storefront create my-shop --preset maia # scaffold + install the suite
|
|
76
|
+
storefront update # refresh an existing install
|
|
77
|
+
storefront doctor # report the recorded version and status
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Create a storefront
|
|
81
|
+
|
|
82
|
+
`create` scaffolds a complete, buildable storefront in one reproducible pass — no
|
|
83
|
+
LLM re-derivation, no hand-editing. The target directory (positional `<dir>`) is the
|
|
84
|
+
project root that will hold `.opencode/`:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
storefront create my-shop \
|
|
88
|
+
--name my-shop \
|
|
89
|
+
--site-name "My Shop" \
|
|
90
|
+
--backend-url http://localhost:9000 \
|
|
91
|
+
--preset maia \
|
|
92
|
+
[--brand orange]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Flag | Meaning | Default |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `<dir>` (positional) | target project directory (created if missing) | required |
|
|
98
|
+
| `--name <name>` | kebab-case storefront name = `lib/<name>/` = `__STOREFRONT__` | prompted in the wizard |
|
|
99
|
+
| `--site-name <name>` | human site name (`__SITE_NAME__`) | `<name>` |
|
|
100
|
+
| `--backend-url <url>` | Medusa backend URL | `http://localhost:9000` |
|
|
101
|
+
| `--preset <preset>` | visual preset: `maia`, `nova`, `vega`, `luma`, `lyra`, `mira` | prompted in the wizard (Maia preselected) |
|
|
102
|
+
| `--brand <color>` | named brand color overriding preset primary/nav/ring tokens (never prompted) | none |
|
|
103
|
+
| `--force`, `-f` | overwrite files that already exist | off |
|
|
104
|
+
|
|
105
|
+
The pipeline runs: preconditions → Next.js base → shadcn base with the preset →
|
|
106
|
+
`.yarnrc.yml` (`nodeLinker: node-modules`) → dependency install → template copy +
|
|
107
|
+
placeholder substitution → default-page removal → `.env.local` + theme → suite
|
|
108
|
+
install into `.opencode/`.
|
|
109
|
+
|
|
110
|
+
Same inputs produce a byte-identical tree (verified by recursive `diff -r`). Running
|
|
111
|
+
`create` twice with different inputs is the only reason the output differs.
|
|
112
|
+
|
|
113
|
+
The suite install copies `commands/` and the shipped skills under `skills/` (own
|
|
114
|
+
skills and vendored skills, flat) into `<dir>/.opencode/`. It verifies its own
|
|
115
|
+
generated copies against their source before copying, records the installed version
|
|
116
|
+
in `.opencode/storefront.json`, and is idempotent: use `--force` to overwrite files
|
|
117
|
+
that already exist.
|
|
118
|
+
|
|
119
|
+
### Run the storefront
|
|
120
|
+
|
|
121
|
+
When `create` finishes it prints the next steps:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd my-shop
|
|
125
|
+
yarn dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The storefront renders its home view — hero, benefits, and category sections — using
|
|
129
|
+
only `@thorprovider/blocks` for UI and `@thorprovider/adapters` for data. Then grow
|
|
130
|
+
it incrementally from OpenCode:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
/sf-add-view product # add a view from its recipe
|
|
134
|
+
/sf-view home add a testimonials section # modify an existing view
|
|
135
|
+
/sf-theme # colors, typography, identity
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Backend configuration
|
|
139
|
+
|
|
140
|
+
Generated projects expect a Medusa backend. `create` writes a `.env.local` with the
|
|
141
|
+
backend URL you passed and blank placeholders for the rest:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
MEDUSA_BACKEND_URL=http://localhost:9000
|
|
145
|
+
MEDUSA_REGION_ID=
|
|
146
|
+
MEDUSA_PUBLISHABLE_KEY=
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Set `MEDUSA_REGION_ID` and `MEDUSA_PUBLISHABLE_KEY` as needed, then restart
|
|
150
|
+
`yarn dev`. The storefront shows no catalog data until these are set (see
|
|
151
|
+
[Backend not configured](#backend-not-configured)). During local development you can
|
|
152
|
+
use the `mock` adapter data source instead of a live backend.
|
|
153
|
+
|
|
154
|
+
### Troubleshooting / Known Issues
|
|
155
|
+
|
|
156
|
+
Each entry below follows the same shape: **cause** → **symptom** → **remedy**.
|
|
157
|
+
|
|
158
|
+
#### Ancestor `.opencode/`
|
|
159
|
+
|
|
160
|
+
- **Cause**: the project is created under a directory tree in which an ancestor
|
|
161
|
+
directory already contains a `.opencode/`.
|
|
162
|
+
- **Symptom**: OpenCode resolves the wrong command set — `/sf-add-view`, `/sf-view`,
|
|
163
|
+
and `/sf-theme` run commands from the ancestor's `.opencode/` instead of the ones
|
|
164
|
+
this package installed, so you see stale or unrelated behaviour.
|
|
165
|
+
- **Remedy**: scaffold in a clean root with no ancestor `.opencode/` (for example a
|
|
166
|
+
fresh directory outside the monorepo), or remove/relocate the ancestor `.opencode/`
|
|
167
|
+
before opening the project.
|
|
168
|
+
|
|
169
|
+
#### Non-empty target directory
|
|
170
|
+
|
|
171
|
+
- **Cause**: `create` runs against a directory whose `lib/<name>/` already exists (a
|
|
172
|
+
previous scaffold with the same name), or whose root is already a Next.js project.
|
|
173
|
+
- **Symptom**: the run stops at the preconditions stage with
|
|
174
|
+
`lib/<name>/ already exists — refusing to overwrite`.
|
|
175
|
+
- **Remedy**: choose a different `--name`, or remove the existing `lib/<name>/`
|
|
176
|
+
directory. To extend an existing storefront instead of creating one, use
|
|
177
|
+
`/sf-add-view`. `create-next-app` also refuses to initialize a directory that is
|
|
178
|
+
already a Next.js project unless it can reuse it; run `create` in an empty directory.
|
|
179
|
+
|
|
180
|
+
#### Missing or invalid `--preset`
|
|
181
|
+
|
|
182
|
+
- **Cause**: `--preset` was omitted (in a non-interactive run) or the value is not one
|
|
183
|
+
of the recognized presets.
|
|
184
|
+
- **Symptom**: the run stops at the input-validation stage with
|
|
185
|
+
`--preset must be one of: maia, nova, vega, luma, lyra, mira`.
|
|
186
|
+
- **Remedy**: pass `--preset` with one of `maia`, `nova`, `vega`, `luma`, `lyra`,
|
|
187
|
+
`mira`. In a terminal the wizard preselects `maia`, so the preset is only required
|
|
188
|
+
on the command line for non-interactive runs.
|
|
189
|
+
|
|
190
|
+
#### Yarn Plug'n'Play / linker
|
|
191
|
+
|
|
192
|
+
- **Cause**: a generated project installed with Yarn's default PnP linker.
|
|
193
|
+
- **Symptom**: Next.js 16's Turbopack fails to resolve the Next.js package
|
|
194
|
+
(`Could not find the Next.js package`).
|
|
195
|
+
- **Remedy**: this is why `create` writes `.yarnrc.yml` with
|
|
196
|
+
`nodeLinker: node-modules` before installing. If you set up the project manually,
|
|
197
|
+
add that file and re-run `yarn install` so a real `node_modules/` is materialized.
|
|
198
|
+
Do not use PnP.
|
|
199
|
+
|
|
200
|
+
#### Backend not configured
|
|
201
|
+
|
|
202
|
+
- **Cause**: `MEDUSA_REGION_ID` / `MEDUSA_PUBLISHABLE_KEY` (or a reachable backend) are
|
|
203
|
+
not set in `.env.local`.
|
|
204
|
+
- **Symptom**: the storefront renders its layout but shows no catalog data.
|
|
205
|
+
- **Remedy**: fill in the variables in [Backend configuration](#backend-configuration)
|
|
206
|
+
and restart `yarn dev`, or use the `mock` adapter data source during local
|
|
207
|
+
development.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## What gets installed
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
<project>/.opencode/
|
|
215
|
+
storefront.json # { version, installedAt } — what generation is installed
|
|
216
|
+
commands/
|
|
217
|
+
sf-add-view.md
|
|
218
|
+
sf-view.md
|
|
219
|
+
sf-theme.md
|
|
220
|
+
skills/
|
|
221
|
+
sf-view-gen/ # assets/templates/views/, assets/recipes/, scripts/validate.mjs
|
|
222
|
+
sf-theme-gen/ # assets/
|
|
223
|
+
nextjs-shadcn/
|
|
224
|
+
shadcn-theming/
|
|
225
|
+
shadcn-component-discovery/
|
|
226
|
+
storefront-best-practices/
|
|
227
|
+
building-storefronts/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Each skill carries the templates, recipes, and scripts it uses inside its own
|
|
231
|
+
directory. Skills address them by relative path, so nothing needs the package to be
|
|
232
|
+
located on disk at generation time.
|
|
233
|
+
|
|
234
|
+
## Commands
|
|
235
|
+
|
|
236
|
+
| Command | Purpose |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `/sf-add-view <view>` | Add one view incrementally (`product`, `category`, `checkout`, `user-panel`) |
|
|
239
|
+
| `/sf-view <view> <change>` | Modify an existing view's spec, preserving structure |
|
|
240
|
+
| `/sf-theme` | Customize OKLCH color tokens (light/dark), typography, and identity metadata |
|
|
241
|
+
|
|
242
|
+
Scaffolding itself is `npx @thorprovider/storefront create <dir>` — there is no
|
|
243
|
+
`/sf-init` command; the wizard lives in the CLI and is deterministic.
|
|
244
|
+
|
|
245
|
+
## Design guarantees
|
|
246
|
+
|
|
247
|
+
- **Self-contained**: each skill carries the assets it uses and addresses them by
|
|
248
|
+
relative path; nothing depends on the Thor Commerce monorepo, on machine-global
|
|
249
|
+
skills, or on locating the package at generation time.
|
|
250
|
+
- **Consistent by construction**: shared recipes are authored once and generated into
|
|
251
|
+
each skill; a check fails when a copy drifts, both at build time and before the
|
|
252
|
+
installer copies anything.
|
|
253
|
+
- **Stack-constrained**: UI comes only from `@thorprovider/blocks`; data only from
|
|
254
|
+
`@thorprovider/adapters` and `@thorprovider/runtime/commerce`.
|
|
255
|
+
- **Varied output**: a recipe registry lets the skills pick block variants and optional
|
|
256
|
+
sections (testimonials, FAQ, statistics, logo cloud, pricing) from your description,
|
|
257
|
+
so sites are not identical.
|
|
258
|
+
|
|
259
|
+
## Generated project structure
|
|
260
|
+
|
|
261
|
+
Each view is a self-contained module. Shared files compose whatever the view barrel
|
|
262
|
+
exports and never name a view, so adding one is a copy plus a single registration.
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
lib/<name>/
|
|
266
|
+
catalog.ts, registry.tsx, runtime.ts, handlers.ts, state.ts # shell; compose the barrel
|
|
267
|
+
mappers.ts # CardProduct -> FeaturedProduct / CategoryCard
|
|
268
|
+
spec/index.ts, spec/page-root.ts, spec/types.ts # routes built from the barrel
|
|
269
|
+
views/
|
|
270
|
+
index.ts # barrel: export const views: ViewModule[] = [home, ...]
|
|
271
|
+
types.ts # ViewModule contract
|
|
272
|
+
sections/*.tsx # optional-section fragments (testimonials, faq, ...)
|
|
273
|
+
home/index.tsx # one folder per view
|
|
274
|
+
product/index.tsx
|
|
275
|
+
category/index.tsx
|
|
276
|
+
checkout/index.tsx
|
|
277
|
+
user-panel/index.tsx
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
A view folder ships the package's default block variants; `/sf-add-view` picks the
|
|
281
|
+
intent/variant from your description (validated by the skill's `scripts/validate.mjs`)
|
|
282
|
+
and substitutes only the imported block identifier. The scaffold templates live at
|
|
283
|
+
the package root, and the view templates travel inside the skills that use them:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
assets/templates/app/ # shell (copied by `create`)
|
|
287
|
+
assets/templates/views/home/ # home (copied by `create`)
|
|
288
|
+
skills/sf-view-gen/assets/templates/views/<view>/ # the other views (copied by /sf-add-view)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
Testing the package during development does not require `npm pack`. The local
|
|
294
|
+
binary reads the working tree directly:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
yarn sync-assets && yarn check-assets # recipes ↔ generated copies are consistent
|
|
298
|
+
yarn test # contract + packaging test suite
|
|
299
|
+
node bin/storefront.js create <tmp> --name my-shop --preset maia # scaffold into a throwaway project
|
|
300
|
+
node bin/storefront.js doctor <tmp> # report the recorded version and status
|
|
301
|
+
node scripts/render-templates.mjs <tmp> my-shop # re-render templates only (no install; fast loop)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`scripts/render-templates.mjs` is the same renderer the `create` action uses for
|
|
305
|
+
stage 6. It copies the templates and substitutes placeholders without touching
|
|
306
|
+
dependencies, so iterating on a template against a persistent project (e.g.
|
|
307
|
+
`projects/<name>/`) costs seconds and no network.
|
|
308
|
+
|
|
309
|
+
`npm pack` exists to verify a different thing: **what actually ships to npm** —
|
|
310
|
+
the `files` list, and that every asset travels inside its skill. It is only
|
|
311
|
+
meaningful at these moments:
|
|
312
|
+
|
|
313
|
+
| Moment | Action |
|
|
314
|
+
| --- | --- |
|
|
315
|
+
| Day-to-day development | Nothing — use the local binary above |
|
|
316
|
+
| Changing `files`, moving assets, adding or removing directories | `npm pack --dry-run` to list what would ship |
|
|
317
|
+
| CI, before publishing | `yarn check-assets` + `npm pack --dry-run` as a guard |
|
|
318
|
+
| Publishing a release | `npm pack` / `yarn npm publish` — the real tarball |
|
|
319
|
+
|
|
320
|
+
A generated `.tgz` goes stale the moment the tree changes, so it is not a
|
|
321
|
+
development tool. Keep it out of the working tree.
|
|
322
|
+
|
|
323
|
+
## Maintenance
|
|
324
|
+
|
|
325
|
+
See `VENDOR.md` for how vendored skills are refreshed and how to bump
|
|
326
|
+
templates.
|
|
327
|
+
|
|
328
|
+
The [Troubleshooting / Known Issues](#troubleshooting--known-issues) entries are part
|
|
329
|
+
of the contract with consumers: revisit them whenever the CLI surface changes
|
|
330
|
+
(flags, actions, validation) or the scaffold's preconditions change (target-directory
|
|
331
|
+
rules, linker, backend variables), and update the corresponding entry in
|
|
332
|
+
`packages/storefront/README.md`. The packaging test suite asserts these
|
|
333
|
+
sections and that every failure-mode entry carries a remedy.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/app/[[...slug]]/page.tsx)
|
|
2
|
+
|
|
3
|
+
import { notFound } from 'next/navigation'
|
|
4
|
+
import { __STOREFRONT_CAMEL__App } from '@/lib/__STOREFRONT__/runtime'
|
|
5
|
+
import { StorefrontRenderer } from './renderer'
|
|
6
|
+
|
|
7
|
+
export const { generateMetadata, generateStaticParams } = __STOREFRONT_CAMEL__App
|
|
8
|
+
|
|
9
|
+
export default async function Page({
|
|
10
|
+
params,
|
|
11
|
+
}: {
|
|
12
|
+
params: Promise<{ slug?: string[] }>
|
|
13
|
+
}) {
|
|
14
|
+
const data = await __STOREFRONT_CAMEL__App.getPageData({ params })
|
|
15
|
+
if (!data) return notFound()
|
|
16
|
+
return <StorefrontRenderer {...data} />
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/app/[[...slug]]/renderer.tsx)
|
|
4
|
+
|
|
5
|
+
import { PageRenderer } from '@json-render/next'
|
|
6
|
+
import type { PageData } from '@json-render/next'
|
|
7
|
+
|
|
8
|
+
export function StorefrontRenderer(data: PageData) {
|
|
9
|
+
return <PageRenderer {...data} />
|
|
10
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "@thorprovider/blocks/styles.css";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--color-card: var(--card);
|
|
11
|
+
--color-card-foreground: var(--card-foreground);
|
|
12
|
+
--color-popover: var(--popover);
|
|
13
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
14
|
+
--color-primary: var(--primary);
|
|
15
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
16
|
+
--color-secondary: var(--secondary);
|
|
17
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
18
|
+
--color-muted: var(--muted);
|
|
19
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
20
|
+
--color-accent: var(--accent);
|
|
21
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
22
|
+
--color-destructive: var(--destructive);
|
|
23
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
24
|
+
--color-success: var(--success);
|
|
25
|
+
--color-success-foreground: var(--success-foreground);
|
|
26
|
+
--color-warning: var(--warning);
|
|
27
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
28
|
+
--color-border: var(--border);
|
|
29
|
+
--color-input: var(--input);
|
|
30
|
+
--color-ring: var(--ring);
|
|
31
|
+
--radius-sm: var(--radius-sm);
|
|
32
|
+
--radius-md: var(--radius-md);
|
|
33
|
+
--radius-lg: var(--radius-lg);
|
|
34
|
+
--radius-xl: var(--radius-xl);
|
|
35
|
+
--font-sans: var(--font-geist-sans);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:root {
|
|
39
|
+
--background: oklch(0.98 0.002 90);
|
|
40
|
+
--foreground: oklch(0.15 0 0);
|
|
41
|
+
--card: oklch(1 0 0);
|
|
42
|
+
--card-foreground: oklch(0.15 0 0);
|
|
43
|
+
--popover: oklch(1 0 0);
|
|
44
|
+
--popover-foreground: oklch(0.15 0 0);
|
|
45
|
+
--primary: oklch(0.15 0 0);
|
|
46
|
+
--primary-foreground: oklch(0.98 0.002 90);
|
|
47
|
+
--secondary: oklch(0.96 0.003 90);
|
|
48
|
+
--secondary-foreground: oklch(0.15 0 0);
|
|
49
|
+
--muted: oklch(0.94 0.003 90);
|
|
50
|
+
--muted-foreground: oklch(0.45 0 0);
|
|
51
|
+
--accent: oklch(0.58 0.16 25);
|
|
52
|
+
--accent-foreground: oklch(1 0 0);
|
|
53
|
+
--destructive: oklch(0.55 0.2 25);
|
|
54
|
+
--destructive-foreground: oklch(0.98 0.002 90);
|
|
55
|
+
--success: oklch(0.47 0.14 155);
|
|
56
|
+
--success-foreground: oklch(0.98 0.01 155);
|
|
57
|
+
--warning: oklch(0.55 0.18 75);
|
|
58
|
+
--warning-foreground: oklch(0.98 0.02 75);
|
|
59
|
+
--border: oklch(0.88 0.003 90);
|
|
60
|
+
--input: oklch(0.92 0.003 90);
|
|
61
|
+
--ring: oklch(0.15 0 0);
|
|
62
|
+
--radius-sm: 0.25rem;
|
|
63
|
+
--radius-md: 0.375rem;
|
|
64
|
+
--radius-lg: 0.5rem;
|
|
65
|
+
--radius-xl: 0.75rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.dark {
|
|
69
|
+
--background: oklch(0.15 0 0);
|
|
70
|
+
--foreground: oklch(0.98 0.002 90);
|
|
71
|
+
--card: oklch(0.2 0 0);
|
|
72
|
+
--card-foreground: oklch(0.98 0.002 90);
|
|
73
|
+
--popover: oklch(0.2 0 0);
|
|
74
|
+
--popover-foreground: oklch(0.98 0.002 90);
|
|
75
|
+
--primary: oklch(0.98 0.002 90);
|
|
76
|
+
--primary-foreground: oklch(0.15 0 0);
|
|
77
|
+
--secondary: oklch(0.25 0.003 90);
|
|
78
|
+
--secondary-foreground: oklch(0.98 0.002 90);
|
|
79
|
+
--muted: oklch(0.25 0.003 90);
|
|
80
|
+
--muted-foreground: oklch(0.65 0 0);
|
|
81
|
+
--accent: oklch(0.65 0.15 25);
|
|
82
|
+
--accent-foreground: oklch(0.15 0 0);
|
|
83
|
+
--destructive: oklch(0.65 0.2 25);
|
|
84
|
+
--destructive-foreground: oklch(0.15 0 0);
|
|
85
|
+
--success: oklch(0.6 0.13 155);
|
|
86
|
+
--success-foreground: oklch(0.1 0.02 155);
|
|
87
|
+
--warning: oklch(0.7 0.15 75);
|
|
88
|
+
--warning-foreground: oklch(0.1 0.02 75);
|
|
89
|
+
--border: oklch(0.3 0.003 90);
|
|
90
|
+
--input: oklch(0.3 0.003 90);
|
|
91
|
+
--ring: oklch(0.85 0 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@layer base {
|
|
95
|
+
* {
|
|
96
|
+
@apply border-border;
|
|
97
|
+
}
|
|
98
|
+
body {
|
|
99
|
+
@apply bg-background text-foreground;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/app/layout.tsx)
|
|
2
|
+
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
import { Geist } from 'next/font/google'
|
|
5
|
+
import { StorefrontProvider } from '@thorprovider/runtime/host-glue/providers'
|
|
6
|
+
import { themeVarsToCss, themeTokensToVars } from '@thorprovider/runtime/render-core/theme'
|
|
7
|
+
import { DEFAULT_THEME_TOKENS } from '@thorprovider/runtime/contracts'
|
|
8
|
+
import { registry } from '@/lib/__STOREFRONT__/registry'
|
|
9
|
+
import './globals.css'
|
|
10
|
+
|
|
11
|
+
const geist = Geist({
|
|
12
|
+
variable: '--font-geist-sans',
|
|
13
|
+
subsets: ['latin'],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const themeCss = themeVarsToCss(themeTokensToVars(DEFAULT_THEME_TOKENS))
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: '__SITE_NAME__',
|
|
20
|
+
description: 'Tienda online de __SITE_NAME__.',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({
|
|
24
|
+
children,
|
|
25
|
+
}: Readonly<{ children: React.ReactNode }>) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="es" className={geist.variable}>
|
|
28
|
+
<body className="font-sans antialiased">
|
|
29
|
+
<StorefrontProvider themeCss={themeCss} registry={registry}>
|
|
30
|
+
{children}
|
|
31
|
+
</StorefrontProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/catalog.ts)
|
|
2
|
+
// Base catalog for the app shell plus every registered view's catalog fragment.
|
|
3
|
+
// View element types live with their view module; the shell owns only chrome.
|
|
4
|
+
|
|
5
|
+
import { defineCatalog } from '@json-render/core'
|
|
6
|
+
import { schema } from '@json-render/react/schema'
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { views } from './views'
|
|
9
|
+
|
|
10
|
+
const ctaSchema = z.object({
|
|
11
|
+
label: z.string(),
|
|
12
|
+
href: z.string().optional(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const navLinkSchema = z.object({
|
|
16
|
+
label: z.string(),
|
|
17
|
+
href: z.string().optional(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const footerLinkSchema = z.object({
|
|
21
|
+
label: z.string(),
|
|
22
|
+
href: z.string().optional(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const footerLinkColumnSchema = z.object({
|
|
26
|
+
title: z.string(),
|
|
27
|
+
links: z.array(footerLinkSchema),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Declared locally so the catalog builder's zod version is the only one in play.
|
|
31
|
+
// Cross-major schema assignment (@thorprovider/blocks ships zod v3) breaks the
|
|
32
|
+
// defineCatalog contract, so the block contract is mirrored here instead.
|
|
33
|
+
const productSchema = z.object({
|
|
34
|
+
id: z.union([z.string(), z.number()]),
|
|
35
|
+
title: z.string(),
|
|
36
|
+
price: z.number(),
|
|
37
|
+
originalPrice: z.number().optional(),
|
|
38
|
+
image: z.string().optional(),
|
|
39
|
+
badge: z.string().optional(),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const baseComponents = {
|
|
43
|
+
Layout: {
|
|
44
|
+
props: z.object({}),
|
|
45
|
+
slots: ['default', 'header', 'footer'],
|
|
46
|
+
description: 'Page layout shell. Pages render inside its default slot.',
|
|
47
|
+
},
|
|
48
|
+
Navbar: {
|
|
49
|
+
props: z.object({
|
|
50
|
+
navLinks: z.array(navLinkSchema),
|
|
51
|
+
logoText: z.string(),
|
|
52
|
+
logoHref: z.string().optional(),
|
|
53
|
+
currentPath: z.string().optional(),
|
|
54
|
+
signIn: ctaSchema.optional(),
|
|
55
|
+
cta: ctaSchema.optional(),
|
|
56
|
+
cartCount: z.number().optional(),
|
|
57
|
+
}),
|
|
58
|
+
description: 'Top navigation bar.',
|
|
59
|
+
},
|
|
60
|
+
ProductCard: {
|
|
61
|
+
props: z.object({
|
|
62
|
+
products: z.array(productSchema),
|
|
63
|
+
heading: z.string().optional(),
|
|
64
|
+
maxItems: z.number().optional(),
|
|
65
|
+
}),
|
|
66
|
+
description: 'Grid of product cards.',
|
|
67
|
+
},
|
|
68
|
+
Footer: {
|
|
69
|
+
props: z.object({
|
|
70
|
+
brandName: z.string(),
|
|
71
|
+
brandDescription: z.string().optional(),
|
|
72
|
+
linkColumns: z.array(footerLinkColumnSchema),
|
|
73
|
+
newsletter: z
|
|
74
|
+
.object({ placeholder: z.string().optional(), buttonLabel: z.string().optional() })
|
|
75
|
+
.optional(),
|
|
76
|
+
}),
|
|
77
|
+
description: 'Site footer.',
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const viewComponents = Object.assign({}, ...views.map((view) => view.catalog))
|
|
82
|
+
|
|
83
|
+
export const catalog = defineCatalog(schema, {
|
|
84
|
+
components: { ...baseComponents, ...viewComponents },
|
|
85
|
+
actions: {},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export type StorefrontCatalog = typeof catalog
|