znode-plugin 0.1.1 → 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 +369 -21
  2. package/package.json +5 -2
package/README.md CHANGED
@@ -1,34 +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.
5
+
6
+ ---
7
+
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
6
37
 
7
38
  ```bash
8
- npm create znode-plugin@latest my-theme # a new project (recommended)
9
- npm install -D znode-plugin # or add the SDK to an existing package
39
+ npm init znode-plugin@latest my-theme
40
+ ```
41
+
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)
10
54
  ```
11
55
 
12
- | Command | What it does |
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).*
58
+
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).
61
+
62
+ ### 2. Run it
63
+
64
+ ```bash
65
+ cd my-theme
66
+ npm run dev
67
+ ```
68
+
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 |
13
83
  | --- | --- |
14
- | `znode build <dir>` | Type-check (with a `tsconfig.json`), then build the server and browser bundles into `dist/` |
15
- | `znode check <dir>` | Marketplace review: metadata, permissions against the code, settings, sizes |
16
- | `znode pack <dir>` | Build and zip into `releases/<name>-<version>.zpkg.zip` |
17
- | `znode install <zip\|dir> [storeDir]` | Install into the store's plugin folder (keeps the store's settings and stores) |
18
- | `znode deploy <dir> [storeDir]` | Pack, then install |
19
- | `znode dev <dir> [storeDir]` | Deploy, then again on every change under `src/` or to `znode-package.json` |
20
- | `znode link-store <storeRepo> [dir]` | Writes `tsconfig.store.json` so `@znode/*` imports get the store's real types |
21
- | `znode create <kind> <name> [dir]` | A minimal starter of a kind (theme, plugin, widget, content) |
22
-
23
- `storeDir` (the store's `RUNTIME_PLUGINS_DIR`): the argument, else the `RUNTIME_PLUGINS_DIR` environment variable, else
24
- `znode.local.json` (`{ "pluginsDir": "…" }`) in the folder the command runs in.
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.
107
+
108
+ ### 6. Release
109
+
110
+ ```bash
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"] }
121
+ ```
122
+
123
+ Deploying again keeps these two fields.
124
+
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 |
132
+ | --- | --- | --- |
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
+ ```
182
+
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
+ ---
279
+
280
+ ## Commands
281
+
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 |
304
+ | --- | --- |
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
+ ---
25
373
 
26
374
  ## Imports in a package
27
375
 
28
376
  | Import | At runtime |
29
377
  | --- | --- |
30
- | `react`, `react/jsx-runtime`, `next/image`, `next/link`, `next/navigation` | The store's own copies (one React, its router and images config) |
31
- | `next/font/google` | Built into the package: the font files are downloaded at build time |
32
- | `@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 |
33
381
 
34
- 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.1",
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
  }