znode-plugin 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/README.md +347 -50
  2. package/package.json +5 -2
package/README.md CHANGED
@@ -1,85 +1,382 @@
1
1
  # znode-plugin
2
2
 
3
- The SDK for Znode runtime plugins and themes: the `znode` command behind every project made with
4
- `create-znode-plugin`. A package is built on its own and runs in the webstore for the stores it is enabled for, with no
5
- fork of the store and no webstore rebuild.
3
+ Build **Znode runtime themes and plugins**: change how a Znode webstore looks and behaves **without forking the store and
4
+ without rebuilding it**. You write a small package, the SDK builds it, and the running webstore loads it.
6
5
 
7
- ## Getting started
6
+ ---
8
7
 
9
- Start a new project with the recipe (any one of these, they are the same):
8
+ ## The story in 30 seconds
9
+
10
+ Before, every customer change (a new header, a product card, a widget) meant editing the webstore's code. Each store
11
+ became a fork, and every Znode upgrade meant re-applying those edits by hand.
12
+
13
+ With runtime packages:
14
+
15
+ ```
16
+ your package (my-theme) the Znode webstore (unchanged)
17
+ ┌────────────────────────┐ npm run dev ┌──────────────────────────────────────┐
18
+ │ src/styles/*.css │ ───────────────▶ │ plugin folder (RUNTIME_PLUGINS_DIR) │
19
+ │ src/browser.tsx (slots,│ build + copy │ plugins.json my-theme-1.0.0.* │
20
+ │ widgets) │ │ │ │
21
+ │ src/server.tsx (pages, │ │ ▼ loaded at runtime │
22
+ │ routes) │ │ every page of the stores it is on │
23
+ └────────────────────────┘ └──────────────────────────────────────┘
24
+ ```
25
+
26
+ - **Theme**: the look of a store (colors, fonts, header, product card, widgets, pages).
27
+ - **Plugin**: a feature (an integration, an API route, a cart rule).
28
+ - One webstore serves many stores; each store gets the packages enabled for it.
29
+
30
+ ---
31
+
32
+ ## Quick start (5 minutes)
33
+
34
+ **You need:** Node.js 20+, a checkout of the Znode webstore repo, and that webstore running locally (`npx nx dev webstore`).
35
+
36
+ ### 1. Create the project
10
37
 
11
38
  ```bash
12
- npm create znode-plugin@latest my-theme
13
- npx create-znode-plugin@latest my-theme
14
39
  npm init znode-plugin@latest my-theme
15
40
  ```
16
41
 
17
- It then asks two things, so have them ready:
42
+ (`npm create znode-plugin@latest my-theme` and `npx create-znode-plugin@latest my-theme` do exactly the same.)
43
+
44
+ It asks **one question: where your webstore is**. Answer it (it is what makes `npm run dev` work), even though the
45
+ prompt offers Enter to skip:
46
+
47
+ ```
48
+ Store path to connect (the store repo, with tsconfig.base.json), Enter to skip: D:\znode\webstore
49
+ Store: D:\znode\webstore
50
+ Plugin folder: D:\znode\runtime-plugins-store
51
+ Created My Theme in D:\themes\my-theme
52
+ Installing dependencies…
53
+ linked D:\themes\my-theme\tsconfig.store.json to D:\znode\webstore\tsconfig.base.json (69 store aliases)
54
+ ```
55
+
56
+ Give it the **root of the webstore repo** (not `apps/webstore`). The plugin folder is read from the store's own settings
57
+ for you. *Why both? See [Why connect a store](#why-connect-a-store).*
18
58
 
19
- 1. **The store path to connect**: the store repo, the folder with `tsconfig.base.json` at its root. The project gets
20
- the store's real types from it (`znode link-store`).
21
- 2. **The store's plugin folder**: where the webstore loads runtime packages from (its `RUNTIME_PLUGINS_DIR`, the folder
22
- with `plugins.json`). Usually found for you in the store's `apps/webstore/.env.local` (or `.env`); asked only when
23
- the store does not set it. `npm run dev` and `npm run deploy` copy your package there.
59
+ > No webstore on this machine yet? Press Enter to skip. The project is still created, but `npm run dev` cannot deploy until
60
+ > you connect it: `npm run link-store -- <webstore repo>` and a `znode.local.json` (see below).
24
61
 
25
- Then:
62
+ ### 2. Run it
26
63
 
27
64
  ```bash
28
65
  cd my-theme
29
66
  npm run dev
30
67
  ```
31
68
 
32
- and, with the webstore running in development mode, preview the theme in your browser only:
33
- `http://localhost:3000/api/extensions/preview?theme=my-theme`. The full walkthrough (every question, what to enter,
34
- skipping, scripts and CI, troubleshooting): [`create-znode-plugin`](https://www.npmjs.com/package/create-znode-plugin).
69
+ This builds the package, copies it into the store's plugin folder, and does it again every time you save a file.
70
+
71
+ ### 3. See it in the store
72
+
73
+ Open in your browser:
74
+
75
+ ```
76
+ http://localhost:3000/api/extensions/preview?theme=my-theme
77
+ ```
78
+
79
+ The store now shows your theme **in your browser only** (a cookie). Other shoppers still see the normal store.
80
+ The starter theme adds an announcement bar above the header and a "New" badge on product cards, so you can see it working.
81
+
82
+ | Preview link | What it does |
83
+ | --- | --- |
84
+ | `/api/extensions/preview?theme=my-theme` | turn on your theme, home page |
85
+ | `/api/extensions/preview?theme=my-theme&to=/product/767` | turn it on and open a page |
86
+ | `/api/extensions/preview` | turn the preview off (the store as it is) |
87
+ | `/api/extensions/preview?list` | list the themes the store can see |
88
+
89
+ ### 4. Change something
90
+
91
+ Edit `src/styles/theme.css`, for example:
92
+
93
+ ```css
94
+ /* make every button in the store rounded */
95
+ [data-test-selector^="btn"] { border-radius: 999px; }
96
+ ```
97
+
98
+ Save. `npm run dev` rebuilds and redeploys; reload the page.
99
+
100
+ ### 5. Check before you ship
101
+
102
+ ```bash
103
+ npm run check
104
+ ```
105
+
106
+ Reviews the package like the marketplace does: manifest, permissions against the code, settings, sizes. Fix every error.
35
107
 
36
- To add the SDK to an existing package instead:
108
+ ### 6. Release
37
109
 
38
110
  ```bash
39
- npm install -D znode-plugin
111
+ npm run pack # releases/my-theme-1.0.0.zpkg.zip (to hand to another store / environment)
112
+ npm run deploy # or: install once into the plugin folder of this machine
113
+ ```
114
+
115
+ ### 7. Turn it on for a store
116
+
117
+ A deployed package is **off for every store** (preview only). To make it live, edit the plugin folder's `plugins.json`:
118
+
119
+ ```json
120
+ { "name": "my-theme", "enabled": true, "stores": ["MyStoreCode"] }
40
121
  ```
41
122
 
42
- ## Connecting a project to a store
123
+ Deploying again keeps these two fields.
43
124
 
44
- | What | How | Saved in |
125
+ ---
126
+
127
+ ## Why connect a store
128
+
129
+ The project lives **outside** the webstore repo (its own git repo), so it needs two links to one store on your machine:
130
+
131
+ | Link | Why | Saved in |
45
132
  | --- | --- | --- |
46
- | The store's types | `npm run link-store -- <store repo>` (`znode link-store <storeRepo> [dir]`) | `tsconfig.store.json` (git-ignored) |
47
- | The store's plugin folder | Answer the recipe's question, or write it yourself: `{ "pluginsDir": "D:\\znode\\runtime-plugins-store" }` | `znode.local.json` (git-ignored) |
133
+ | **Store types** (the repo root) | Your code can import the store's own components and types (`@znode/base-components/...`) and TypeScript checks them against the real store code. Without it, those imports are red in the editor and `npm run build` cannot type-check them. | `tsconfig.store.json` |
134
+ | **Plugin folder** (`RUNTIME_PLUGINS_DIR`) | Where `npm run dev` / `deploy` copies the built package, so the running webstore picks it up. | `znode.local.json` |
135
+
136
+ Both files are **per machine** and git-ignored: each developer connects their own checkout.
137
+
138
+ ```bash
139
+ npm run link-store -- D:\znode\webstore # (re)connect the store types
140
+ echo { "pluginsDir": "D:\\znode\\runtime-plugins-store" } > znode.local.json # set the plugin folder by hand
141
+ ```
142
+
143
+ Run `link-store` again after the store adds or moves modules.
144
+
145
+ ---
146
+
147
+ ## What is in the project
148
+
149
+ ```
150
+ my-theme/
151
+ znode-package.json name, version, kind ("theme"), parent, settings, widgets, pages ← the manifest
152
+ src/browser.tsx slots and page-builder widgets (runs on the server first, then in the browser)
153
+ src/server.tsx server-only: layouts around pages, pages at new URLs, API routes
154
+ src/slots/ AnnouncementBar (layout.header), ProductBadge (product.card)
155
+ src/widgets/Hero.tsx a page-builder widget
156
+ src/server/ ThemeLayout (font on every page), WelcomePage (/my-theme-welcome)
157
+ src/styles/theme.css CSS linked into every page
158
+ src/i18n/labels.json text (labels) the theme changes
159
+ ```
160
+
161
+ ---
162
+
163
+ ## How do I ...? (examples)
164
+
165
+ ### Change colors and fonts
166
+
167
+ ```css
168
+ /* src/styles/theme.css: the store's design tokens */
169
+ html:root {
170
+ --primary: #4f9d39;
171
+ --link: #4f9d39;
172
+ --navigation-bar-bg: #3f5fb8;
173
+ }
174
+ ```
175
+
176
+ Google fonts, as in any Next.js app (`src/server/fonts.ts`), applied by the server slot `layout.root`:
177
+
178
+ ```ts
179
+ import { Urbanist } from "next/font/google";
180
+ export const urbanist = Urbanist({ subsets: ["latin"], weight: ["400", "700"], variable: "--font-urbanist" });
181
+ ```
48
182
 
49
- Both are per machine: each developer connects their own checkout. Run `link-store` again after the store adds or moves
50
- modules.
183
+ ### Restyle or hide part of a store page
184
+
185
+ Every store element has a `data-test-selector`. Target it in CSS:
186
+
187
+ ```css
188
+ /* hide the Job Name field at checkout */
189
+ [data-test-selector="AdditionalInfoContainer"] form > div:has([data-test-selector="txtJobProjectHeadline"]) {
190
+ display: none;
191
+ }
192
+ ```
193
+
194
+ ### Add something to a store part (a slot)
195
+
196
+ A slot gets the store's own rendering as `children`: keep it, add to it, or replace it.
197
+
198
+ ```tsx
199
+ // src/slots/ProductBadge.tsx
200
+ import type { ReactNode } from "react";
201
+
202
+ export function ProductBadge(props: { children?: ReactNode; product?: { isNew?: boolean } }) {
203
+ return (
204
+ <div className="my-theme-card">
205
+ {props.product?.isNew && <span className="my-theme-badge">New</span>}
206
+ {props.children /* the store's card, unchanged */}
207
+ </div>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ```tsx
213
+ // src/browser.tsx
214
+ export default function setup(host: IBrowserHost): IBrowserPlugin {
215
+ bindHost(host);
216
+ return { slots: { "product.card": ProductBadge } };
217
+ }
218
+ ```
219
+
220
+ **Slots:** `layout.header`, `layout.footer`, `header.search`, `product.card`, `product.image`, `product.variants`,
221
+ `product.addToCart`, `product.layout`, `productList.layout`, `addToCart.notification`, `login.form`,
222
+ `checkout.address`, `checkout.shippingOptions`. Server slots (in `server.tsx`): `layout.root`, `login.layout`, `login.page`.
223
+
224
+ ### Add a page-builder widget
225
+
226
+ ```tsx
227
+ // src/widgets/Hero.tsx
228
+ export function Hero(props: { heading?: string }) {
229
+ return <section className="my-theme-hero"><h2>{props.heading}</h2></section>;
230
+ }
231
+ export const HERO_EDITOR = {
232
+ label: "My Hero",
233
+ fields: { heading: { type: "text", label: "Heading" } },
234
+ defaultProps: { heading: "Welcome" },
235
+ };
236
+ ```
237
+
238
+ Register it in `browser.tsx` (`widgets: { MyThemeHero: { render: Hero, ...HERO_EDITOR } }`) and list
239
+ `"widgets": ["MyThemeHero"]` in `znode-package.json`. Merchants then find it in the page builder.
240
+
241
+ ### Let the merchant change a value (settings)
242
+
243
+ ```json
244
+ "settingsSchema": {
245
+ "announcement": { "type": "string", "label": "Announcement text", "browser": true, "default": "Free shipping" },
246
+ "hidePrices": { "type": "boolean", "label": "Hide prices", "browser": true, "attribute": "HidePrices" }
247
+ }
248
+ ```
249
+
250
+ `"attribute"` reads the value from a Znode **global attribute** of the store (set in Znode Admin), on the server, per store.
251
+ Read it in browser code with `settings` from `src/lib/host.ts`.
252
+
253
+ ### Change a text (label)
254
+
255
+ ```json
256
+ // src/i18n/labels.json
257
+ { "Common": { "addToCart": "Add to Bag" } }
258
+ ```
259
+
260
+ ### Add a page at a new URL, or an API route
261
+
262
+ ```tsx
263
+ // src/server.tsx
264
+ serverPages: { "/my-theme-welcome": { render: () => <h1>Welcome</h1>, metadata: { title: "Welcome" } } },
265
+ routes: { hello: async (_request, context) => ({ store: context.store.code }) }, // GET /api/extensions/plugins/my-theme/hello
266
+ ```
267
+
268
+ List the page in `znode-package.json` (`"serverPages": ["/my-theme-welcome"]`); routes need `"permissions": ["routes"]`.
269
+
270
+ ### Build a theme on top of another theme
271
+
272
+ ```json
273
+ "parent": "my-base-theme"
274
+ ```
275
+
276
+ The parent's styles, labels, settings and slots apply first; yours on top.
277
+
278
+ ---
51
279
 
52
280
  ## Commands
53
281
 
54
- | Command | What it does |
282
+ Run them with `npm run <name>` inside a project (the scripts call `znode … .`):
283
+
284
+ | npm script | `znode` command | What it does |
285
+ | --- | --- | --- |
286
+ | `npm run dev` | `znode dev .` | build + deploy, again on every save |
287
+ | `npm run build` | `znode build .` | type check, then bundles into `dist/` |
288
+ | `npm run check` | `znode check .` | marketplace review (0 errors before you ship) |
289
+ | `npm run pack` | `znode pack .` | build and zip into `releases/<name>-<version>.zpkg.zip` |
290
+ | `npm run deploy` | `znode deploy .` | pack, then install into the plugin folder |
291
+ | `npm run link-store -- <store repo>` | `znode link-store <repo> .` | connect the store's types |
292
+ | | `npx znode install <zip> [pluginsDir]` | install a released zip into a store's plugin folder |
293
+ | | `npx znode create <theme\|plugin\|widget\|content> <name>` | a minimal starter of another kind |
294
+
295
+ The plugin folder is taken from: the command's argument, else the `RUNTIME_PLUGINS_DIR` environment variable, else
296
+ `znode.local.json`.
297
+
298
+ ### `npm run dev` or `npm run deploy`?
299
+
300
+ Both build the package and install it into the plugin folder. `dev` then keeps watching and installs again on every save;
301
+ `deploy` installs once and exits.
302
+
303
+ | Use | When |
55
304
  | --- | --- |
56
- | `znode build <dir>` | Type-check (with a `tsconfig.json`), then build the server and browser bundles into `dist/` |
57
- | `znode check <dir>` | Marketplace review: metadata, permissions against the code, settings, sizes |
58
- | `znode pack <dir>` | Build and zip into `releases/<name>-<version>.zpkg.zip` |
59
- | `znode install <zip\|dir> [storeDir]` | Install into the store's plugin folder (keeps the store's settings and stores) |
60
- | `znode deploy <dir> [storeDir]` | Pack, then install |
61
- | `znode dev <dir> [storeDir]` | Deploy, then again on every change under `src/` or to `znode-package.json` |
62
- | `znode link-store <storeRepo> [dir]` | Writes `tsconfig.store.json` so `@znode/*` imports get the store's real types |
63
- | `znode create <kind> <name> [dir]` | A minimal starter of a kind (theme, plugin, widget, content) |
64
-
65
- `storeDir` (the store's `RUNTIME_PLUGINS_DIR`): the argument, else the `RUNTIME_PLUGINS_DIR` environment variable, else
66
- `znode.local.json` (`{ "pluginsDir": "…" }`) in the folder the command runs in. With none of them, `deploy`, `install`
67
- and `dev` stop with `needs the store's plugin folder`.
68
-
69
- ## After deploying
70
-
71
- - **Preview** (your browser only, webstore in development mode):
72
- `/api/extensions/preview?theme=<name>`, `…&to=/<path>` to open a page, `/api/extensions/preview` to stop.
73
- `/api/extensions/preview?list` lists the themes the store can see.
74
- - **Live for a store**: in the plugin folder's `plugins.json`, set the package's `"enabled": true` and add the store
75
- codes to `"stores"`. Deploying again keeps those settings.
305
+ | `npm run dev` | **While you write code** (the everyday command). Save, wait for `installed theme my-theme@…`, reload the page. You do not also run `deploy`. |
306
+ | `npm run deploy` | **One install, no watching**: a fresh machine, after `git pull`, in a script or CI (it finishes; `dev` never does). |
307
+ | `npm run pack` | **To hand the package to another store or environment**: a zip, installed there with `npx znode install`. |
308
+
309
+ Like `next dev` and `next build`. Note: the store does not refresh an open page by itself; reload it after each install.
310
+
311
+ ---
312
+
313
+ ## Versions
314
+
315
+ The version that counts is **`"version"` in `znode-package.json`**. It names the installed files
316
+ (`my-theme-1.1.0.browser.mjs`, `my-theme-1.1.0.css`), so shoppers' browsers fetch new files instead of cached ones, and it
317
+ is written to the package's entry in `plugins.json`. Installing keeps that entry's `enabled`, `stores` and `settings`.
318
+
319
+ | Change | Bump | Example |
320
+ | --- | --- | --- |
321
+ | A fix, nothing new for merchants | patch | 1.1.0 → 1.1.1 |
322
+ | New features (a slot, widget, page, setting), nothing removed | minor | 1.1.0 → 1.2.0 |
323
+ | Breaking: a setting or widget field removed or renamed (saved pages break), the parent theme changed | major | 1.2.0 → 2.0.0 |
324
+
325
+ **While developing, do not bump:** `npm run dev` re-installs the same version on every save, which is expected.
326
+
327
+ **To release a version, by hand:**
328
+
329
+ 1. Edit `"version"` in `znode-package.json` (and the same in `package.json` to keep them equal; `npm version` changes only
330
+ `package.json`, which the store does not read).
331
+ 2. Check and pack:
332
+
333
+ ```bash
334
+ npm run check
335
+ npm run pack # releases/my-theme-1.2.0.zpkg.zip
336
+ ```
337
+
338
+ 3. Install it: `npm run deploy` here (prints `installed theme my-theme@1.2.0 (was 1.1.0)`), or into another store:
339
+
340
+ ```bash
341
+ npx znode install releases/my-theme-1.2.0.zpkg.zip <that store's plugin folder>
342
+ ```
343
+
344
+ 4. Check `plugins.json`: `"version": "1.2.0"`, with `enabled`, `stores` and `settings` unchanged.
345
+ 5. Commit the version change and tag it:
346
+
347
+ ```bash
348
+ git commit -am "my-theme 1.2.0"
349
+ git tag my-theme@1.2.0
350
+ git push --tags
351
+ ```
352
+
353
+ **Other numbers in the manifest:** `"apiVersion": 1` is the runtime API the package is written for (set by the SDK; do not
354
+ change it), and `"compatibility": { "webstore": ">=10.14.0" }` says which webstore releases it supports.
355
+
356
+ A parent theme and its child have their own versions: bump the one that changed. Old versions' files stay in the plugin
357
+ folder after an upgrade; delete them by hand if you need the space.
358
+
359
+ ---
360
+
361
+ ## Troubleshooting
362
+
363
+ | You see | Do this |
364
+ | --- | --- |
365
+ | `needs the store's plugin folder` | create `znode.local.json` (see [Why connect a store](#why-connect-a-store)) |
366
+ | `@znode/...` imports are red | `npm run link-store -- <store repo>` |
367
+ | Preview link returns 404 / no change | is the webstore running in **dev** mode? did `npm run dev` print `installed theme …` (no errors)? reload the page |
368
+ | `installed …` but still no change | the package went to another plugin folder than the running webstore reads: compare `znode.local.json` with the webstore's `RUNTIME_PLUGINS_DIR` (`apps/webstore/.env.local` or `.env`) |
369
+ | `MISSING_MESSAGE` for your label | put browser labels under a namespace the page sends, e.g. `Common` |
370
+ | Theme shows for you but not shoppers | expected: enable it in `plugins.json` (step 7) |
371
+
372
+ ---
76
373
 
77
374
  ## Imports in a package
78
375
 
79
376
  | Import | At runtime |
80
377
  | --- | --- |
81
- | `react`, `react/jsx-runtime`, `next/image`, `next/link`, `next/navigation` | The store's own copies (one React, its router and images config) |
82
- | `next/font/google` | Built into the package: the font files are downloaded at build time |
83
- | `@znode/…` store modules the store publishes | The store's own modules, in browser parts |
378
+ | `react`, `next/image`, `next/link`, `next/navigation`, `next-intl` | the store's own copies |
379
+ | `next/font/google` | downloaded and built into the package |
380
+ | `@znode/…` (store modules) | the store's own modules |
84
381
 
85
- Types for the package API (slots, widgets, server slots, server pages, routes, hooks): `znode-plugin/types/extension-api`.
382
+ API types (slots, widgets, server pages, routes, hooks): `znode-plugin/types/extension-api`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "znode-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Build, check, pack and deploy Znode runtime plugins and themes: the SDK behind every create-znode-plugin project.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -28,5 +28,8 @@
28
28
  "rollup": "4.59.0",
29
29
  "typescript": "5.9.3"
30
30
  },
31
- "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" }
31
+ "publishConfig": {
32
+ "registry": "https://registry.npmjs.org/",
33
+ "access": "public"
34
+ }
32
35
  }