@fluid-app/fluid-cli-member-site 0.0.0-stage → 0.1.0
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/README.md +456 -2
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +5164 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +83 -3
package/README.md
CHANGED
|
@@ -1,3 +1,457 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @fluid-app/fluid-cli-member-site
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fluid CLI plugin for member sites. Adds `fluid member-site`, which works with
|
|
4
|
+
the member site API and the company member sites API: it lists, creates, pulls,
|
|
5
|
+
pushes, publishes, lints and previews sites.
|
|
6
|
+
|
|
7
|
+
This is not `fluid mysite`, which is an unrelated command, and not `fluid theme`.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @fluid-app/fluid-cli @fluid-app/fluid-cli-member-site
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires `@fluid-app/fluid-cli` as the core CLI.
|
|
16
|
+
|
|
17
|
+
## Which login builds which site
|
|
18
|
+
|
|
19
|
+
The credential follows the owner of the site:
|
|
20
|
+
|
|
21
|
+
| Site owner | API | Login |
|
|
22
|
+
| ---------- | ------------------------------------ | --------------------------------------- |
|
|
23
|
+
| Member | `/api/member/v2026-10/sites` | The member's own login |
|
|
24
|
+
| Company | `/api/company/v2026-10/member-sites` | A signed-in admin who may change themes |
|
|
25
|
+
|
|
26
|
+
The plugin keeps no credential of its own. It uses the login, profiles and
|
|
27
|
+
`.fluidrc` of the core CLI, so `FLUID_TOKEN`, a project's `.fluidrc` profile and
|
|
28
|
+
the active profile apply as they do for every other command. The API base
|
|
29
|
+
follows the login: `FLUID_API_BASE` if set, else the base the stored profile was
|
|
30
|
+
made for (a `.fluidrc` profile, else the active one). There is no default base,
|
|
31
|
+
so a token from `FLUID_TOKEN`, or a profile with no recorded base, needs
|
|
32
|
+
`FLUID_API_BASE`. With neither, the command stops with "No API base for this
|
|
33
|
+
login: run `fluid login` or set FLUID_API_BASE" before it makes any request.
|
|
34
|
+
|
|
35
|
+
The core CLI stores a token and does not record whose it is, so the plugin
|
|
36
|
+
cannot tell a member's token from an admin's before it sends a request. Say
|
|
37
|
+
which it is with `--as member` or `--as admin`, and a site of the other owner
|
|
38
|
+
kind is refused before any request, with the login it needs. Without `--as`,
|
|
39
|
+
the server decides, and a refusal names the login the site needs.
|
|
40
|
+
|
|
41
|
+
A member who is also an admin of the same company is refused by the member API
|
|
42
|
+
and builds company sites only.
|
|
43
|
+
|
|
44
|
+
`fluid login` signs in admins and reps only. A member signs in to this command
|
|
45
|
+
with the member's own token, set in `FLUID_TOKEN`.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
### `fluid member-site list`
|
|
50
|
+
|
|
51
|
+
With a member login, lists the member's own sites. With an admin login, lists
|
|
52
|
+
every site in the company with its owner and status. A member's site is shown
|
|
53
|
+
read-only: its owner builds it.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
fluid member-site list
|
|
57
|
+
fluid member-site list --as member
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Without `--as`, the company API is tried first and the member API when the
|
|
61
|
+
company API refuses the login.
|
|
62
|
+
|
|
63
|
+
### `fluid member-site create <handle>`
|
|
64
|
+
|
|
65
|
+
Creates a site as a draft. The handle is lowercase letters, digits and single
|
|
66
|
+
hyphens, at most 40 characters. The name defaults to the handle in words.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
fluid member-site create rileys-shop --name "Riley's Shop" # a member's site
|
|
70
|
+
fluid member-site create fall-launch --company --as admin # a company site
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`--company` creates a company-owned site and needs an admin login.
|
|
74
|
+
|
|
75
|
+
### `fluid member-site pull <site> [dir]`
|
|
76
|
+
|
|
77
|
+
Pulls a site into a local folder. `<site>` is the site's handle or id, and
|
|
78
|
+
`dir` defaults to the handle. Every text file is written at its API path, the
|
|
79
|
+
pages are written to `config/pages.json`, and a binding file records where the
|
|
80
|
+
folder came from.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
fluid member-site pull rileys-shop --as member # a member's site
|
|
84
|
+
fluid member-site pull fall-launch ./site --as admin # a company site
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
rileys-shop/
|
|
89
|
+
layouts/site.liquid sections/hero.liquid snippets/card.liquid
|
|
90
|
+
pages/index.liquid pages/about.liquid
|
|
91
|
+
assets/site.css assets/site.js
|
|
92
|
+
config/settings.json config/menus.json config/pages.json
|
|
93
|
+
config/access.json (only when the site declares what it uses)
|
|
94
|
+
.member-site.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- The folder is the API: a path here is the path the file API uses. The home
|
|
98
|
+
page is `pages/index.liquid`.
|
|
99
|
+
- `config/access.json` is the site's declaration of the viewer data it uses and
|
|
100
|
+
the actions it takes as the viewer: `{ "uses": ["member.email", "orders: read"] }`.
|
|
101
|
+
A member site has it once it has a draft declaration, and a company site never
|
|
102
|
+
does. It is a draft until the site is published.
|
|
103
|
+
- `config/pages.json` holds the site's default `layout` and, for each page, its
|
|
104
|
+
`slug`, `title`, `position`, `published`, `layout` and `menu` (the menu's
|
|
105
|
+
slug). It holds no page content, which is in `pages/<slug>.liquid`. A page
|
|
106
|
+
whose content is a template the site does not own has a row there and no
|
|
107
|
+
file.
|
|
108
|
+
- `.member-site.json` is the binding file. It records the site's `site_id`,
|
|
109
|
+
`handle` and `owner_kind`, the `api_base` it was pulled from, the settings
|
|
110
|
+
`lock_version`, the site's default `layout`, each page's `id`, `lock_version`
|
|
111
|
+
and row by slug (the row lets a push tell which fields you changed), and for every
|
|
112
|
+
file the `lock_version`, `updated_at` and the `version` the file list
|
|
113
|
+
reports (a binding from a pull before the API reported one has none), plus a
|
|
114
|
+
SHA-256 of the content written. It is local state and is never pushed.
|
|
115
|
+
- A site holds text assets only (css, js, json, txt, html). The file list holds
|
|
116
|
+
no other file, and a listed asset that is not text is skipped and named in
|
|
117
|
+
the summary. Use images and fonts from the media library by URL.
|
|
118
|
+
- A pull reads every file before it writes any, so a failed read leaves the
|
|
119
|
+
folder as it was. A file already identical on disk is left alone, so a second
|
|
120
|
+
pull of an unchanged site changes nothing.
|
|
121
|
+
- A folder that is not empty and is not bound to this site is refused, and so
|
|
122
|
+
is a pull that would overwrite a file edited since the last pull. `--force`
|
|
123
|
+
writes anyway. Files that are not on the server are never removed, with one
|
|
124
|
+
exception: when the site no longer has `config/access.json` and the local copy
|
|
125
|
+
is unchanged since the last pull or push, the pull deletes it, so the next push
|
|
126
|
+
does not bring back a declaration another writer removed. An edited copy is
|
|
127
|
+
kept unless `--force` is passed.
|
|
128
|
+
- A pull does not write through a symbolic link. A file or folder of the site
|
|
129
|
+
that is, or is inside, a link is refused, with or without `--force`, before
|
|
130
|
+
any file is written. Each file is written to a new file that replaces the old
|
|
131
|
+
one.
|
|
132
|
+
- A member's site cannot be pulled with an admin login, because the company
|
|
133
|
+
routes refuse a member's site. With `--as admin`, or in a folder bound to a
|
|
134
|
+
member's site, that stops before any file or page request, and names the login
|
|
135
|
+
the site needs.
|
|
136
|
+
|
|
137
|
+
### `fluid member-site push [dir]`
|
|
138
|
+
|
|
139
|
+
Pushes the changes in a pulled folder to its site. Run it in the folder, or pass
|
|
140
|
+
the folder. The folder's binding names the site and its owner kind, so a member's
|
|
141
|
+
site goes through the member API and a company site through the company API, and
|
|
142
|
+
a login of the other kind is refused before any request.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
fluid member-site push --dry-run # print the plan, send nothing
|
|
146
|
+
fluid member-site push --as member
|
|
147
|
+
fluid member-site push --publish # push, then publish once
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A push compares the folder with the binding and sends only what changed. A pull
|
|
151
|
+
followed by a push with no edit sends nothing.
|
|
152
|
+
|
|
153
|
+
Every write and delete carries the version the binding recorded for it, and the
|
|
154
|
+
server refuses it with 409 if the file or page changed since. A push never
|
|
155
|
+
overwrites or deletes a copy that moved on the server. A path the binding never
|
|
156
|
+
saw is created with `must_not_exist`, so a file another writer created after the
|
|
157
|
+
pull is not replaced.
|
|
158
|
+
|
|
159
|
+
What it sends, in this order:
|
|
160
|
+
|
|
161
|
+
1. `config/settings.json`, with the settings `lock_version` the binding recorded.
|
|
162
|
+
It goes first because every page write moves that version, so a settings write
|
|
163
|
+
that came after one would answer 409 to the push's own work.
|
|
164
|
+
2. Each changed or new layout, section, snippet and asset, and `config/access.json`
|
|
165
|
+
(`PUT files/:path`), in path order, so the declaration goes ahead of the
|
|
166
|
+
layouts. A changed file carries its recorded `version`; a new one carries
|
|
167
|
+
`must_not_exist`. On a company site the push leaves `config/access.json`
|
|
168
|
+
alone and says so, as the server refuses the path.
|
|
169
|
+
3. Each new page (`POST pages`) with its file's content, then each changed page
|
|
170
|
+
(`PATCH pages/:id`) with the changed row fields and the changed content, each
|
|
171
|
+
with the page's `lock_version`.
|
|
172
|
+
4. `config/menus.json`, when it changed, with its recorded `version`. It replaces
|
|
173
|
+
every menu of the site. A menus write moves the site's version and the version
|
|
174
|
+
of every page assigned to a menu it drops, so the push reads the pages again
|
|
175
|
+
before the next step.
|
|
176
|
+
5. Each page's menu assignment (`PATCH pages/:id` with `menu`), with the page's
|
|
177
|
+
re-read version. A page body never carries `menu`, so it is never sent before
|
|
178
|
+
its menu exists.
|
|
179
|
+
6. Each removed page (`DELETE pages/:id?lock_version=`), then each removed
|
|
180
|
+
layout, section, snippet, asset and `config/access.json` (`DELETE files/:path?version=`;
|
|
181
|
+
removing the declaration clears its draft). Removals go
|
|
182
|
+
last, so a write that fails never leaves a menu pointing at a page that is
|
|
183
|
+
gone.
|
|
184
|
+
|
|
185
|
+
After each response the binding takes the versions it returned and is saved, so
|
|
186
|
+
a push that stops can be run again after a pull. A removed page or file leaves the
|
|
187
|
+
binding when the server removes it. After the last write the push reads the
|
|
188
|
+
settings once more to record their version, and keeps it only when the settings
|
|
189
|
+
are still the ones in the folder. A second push straight after the first sends
|
|
190
|
+
nothing and does not conflict with it. A push without `--publish` never
|
|
191
|
+
publishes. A publish by someone else moves the version of every page, layout,
|
|
192
|
+
section and snippet it publishes, so the push stops at the first of those it
|
|
193
|
+
writes: pull again first.
|
|
194
|
+
|
|
195
|
+
A page is removed by taking its row out of `config/pages.json` and its file out of
|
|
196
|
+
the folder. A page renamed in `config/pages.json` is a new page and a removal: the
|
|
197
|
+
new page is created first and the old one is deleted after it. `config/settings.json`
|
|
198
|
+
and `config/menus.json` cannot be removed. If either file is missing, the push says
|
|
199
|
+
so and leaves the server's copy.
|
|
200
|
+
|
|
201
|
+
What it lists and does not send: a change to the site's default `layout` in
|
|
202
|
+
`config/pages.json`, which is a change to the site row.
|
|
203
|
+
|
|
204
|
+
What takes effect without a publish. Before the first write, the push lists the
|
|
205
|
+
files and rows that are live as soon as they are written: assets (changed, new or
|
|
206
|
+
removed), any removed file, `config/menus.json`, and page rows (a new page, a
|
|
207
|
+
changed row field, a menu assignment, a removed page). Changes to layouts,
|
|
208
|
+
sections, snippets, page content and the settings are saved as drafts, but
|
|
209
|
+
removing a layout, section or snippet deletes it and its published copy at once,
|
|
210
|
+
so the site stops rendering it without a publish. A push that changes none of
|
|
211
|
+
these lists none.
|
|
212
|
+
|
|
213
|
+
Without `--publish`, a push never calls the publish route.
|
|
214
|
+
|
|
215
|
+
Everything that can be refused is refused before the first request, and every
|
|
216
|
+
refusal is listed at once:
|
|
217
|
+
|
|
218
|
+
- a file that is not a text asset: an asset must be css, js, json, txt or html,
|
|
219
|
+
valid UTF-8 and not empty. Use images and fonts from the media library by URL;
|
|
220
|
+
- a path outside `layouts`, `sections`, `snippets`, `pages`, `assets` and
|
|
221
|
+
`config`, a path in a subfolder, a symbolic link, and a name outside lowercase
|
|
222
|
+
letters, digits, hyphens and underscores (a page's name is lowercase letters
|
|
223
|
+
and digits separated by single hyphens). Names that start with a dot are ignored;
|
|
224
|
+
- a page file with no row in `config/pages.json`, and a new page row with no
|
|
225
|
+
file; a file for a page whose template the site does not own;
|
|
226
|
+
- a page row removed while its file is still in the folder, and a page file
|
|
227
|
+
removed while its row is still in `config/pages.json`: a page goes with its
|
|
228
|
+
file, so remove both or neither;
|
|
229
|
+
- a changed, new-to-the-binding or removed file, or changed menus, whose version
|
|
230
|
+
the binding does not hold (a binding from before the API reported versions).
|
|
231
|
+
Pull the site again first;
|
|
232
|
+
- a `config/pages.json` that is not valid, a slug `index` (the home page is
|
|
233
|
+
`/`), a menu that `config/menus.json` does not define, settings that are not a
|
|
234
|
+
JSON object, and menus that are not JSON or not shaped as the API takes them
|
|
235
|
+
(an object with a `menus` array, and the required keys and types of each menu
|
|
236
|
+
and item; whether a linked page exists is left to the server);
|
|
237
|
+
- new pages that do not fit in the site's 50-page limit. Removals are sent after
|
|
238
|
+
the new pages are created, so a renamed page's old page still counts. Push the
|
|
239
|
+
removals on their own first. This needs the site's current page count, so
|
|
240
|
+
`--dry-run`, which sends no request, does not check it and says so;
|
|
241
|
+
- a menu link whose `url` the server's URL parser refuses: anything but a
|
|
242
|
+
well-formed `http` or `https` address with a host, as written. A space or
|
|
243
|
+
non-ASCII character in the address, or a space around it, is refused rather
|
|
244
|
+
than corrected.
|
|
245
|
+
|
|
246
|
+
A `--dry-run` prints the plan, removals included, and sends no request. With
|
|
247
|
+
`--publish` it also says that a publish would follow.
|
|
248
|
+
|
|
249
|
+
The first stale version stops the push. It names the file or page, tells you to
|
|
250
|
+
pull, lists what was already written, and sends nothing after it. A path that was
|
|
251
|
+
to be created and now exists on the server is reported as created by another
|
|
252
|
+
writer, and a removal the server refused is reported as not deleted. The push is
|
|
253
|
+
not atomic: the API has no batch write, so what was written stays written. A stale
|
|
254
|
+
settings version also follows any page another writer changed, because the
|
|
255
|
+
settings version is the site's.
|
|
256
|
+
|
|
257
|
+
With `--publish`, the push calls the publish route once, after the last write
|
|
258
|
+
succeeded, and prints what `publish` prints (below). It is never called after a
|
|
259
|
+
write failed or a change was refused, and a push with nothing to send still
|
|
260
|
+
publishes. If the publish itself is refused, the pushed changes stay saved as
|
|
261
|
+
drafts and the command says the site was not published.
|
|
262
|
+
|
|
263
|
+
### `fluid member-site publish [dir]`
|
|
264
|
+
|
|
265
|
+
Publishes the site of a pulled folder: the drafts of its layouts, sections,
|
|
266
|
+
snippets, pages and settings become the live site. Run it in the folder, or pass
|
|
267
|
+
the folder. A member's site is published through the member API
|
|
268
|
+
(`POST sites/:id/publish`) with the member's login, and a company site through
|
|
269
|
+
the company API (`POST member-sites/:id/publish`) with an admin's login, chosen
|
|
270
|
+
by the binding as for `push`.
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
fluid member-site publish
|
|
274
|
+
fluid member-site publish ./rileys-shop --as member
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
It makes one publish request and sends no file, so a local edit that was not
|
|
278
|
+
pushed is not published. It prints the site's `status` and its new
|
|
279
|
+
`content_version`.
|
|
280
|
+
|
|
281
|
+
- A site that an admin took down cannot be published again. The command prints
|
|
282
|
+
the API's message, exits non-zero and does not retry.
|
|
283
|
+
- A 409 is reported as a conflict: the site was changed while it was being
|
|
284
|
+
published, so it was not published. Run the command again.
|
|
285
|
+
- A publish moves the version of every layout, section, snippet and page file it
|
|
286
|
+
publishes, of every page, and of the settings. After it succeeds the command
|
|
287
|
+
records a new version in the binding only when it can prove the publish
|
|
288
|
+
published what the folder holds, so the next push does not conflict with the
|
|
289
|
+
publish but still finds a change another writer made. A layout, section,
|
|
290
|
+
snippet or page file takes the server's version when it is the one computed
|
|
291
|
+
from the file's content in the folder. A page takes its `lock_version` when
|
|
292
|
+
that moved by exactly one. The settings take the site's `lock_version` when
|
|
293
|
+
the publish response shows the settings the folder holds and the version moved
|
|
294
|
+
by what one publish moves it. Anything else keeps its old version, the command
|
|
295
|
+
names it, and the next push stops on it and asks for a pull. A file edited in
|
|
296
|
+
the folder and not pushed is not proven either, and keeps its old version, so
|
|
297
|
+
push it before publishing. Assets and menus are not changed by a publish, and
|
|
298
|
+
keep their versions. If the versions cannot be read after the publish, the site
|
|
299
|
+
is still published, and the command says to pull before the next push.
|
|
300
|
+
|
|
301
|
+
### `fluid member-site lint [dir]`
|
|
302
|
+
|
|
303
|
+
Checks a site folder on disk and makes no request. It needs no login and no
|
|
304
|
+
binding file, so it works on a folder that was never pulled. Run it in the
|
|
305
|
+
folder, or pass the folder.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
fluid member-site lint
|
|
309
|
+
fluid member-site lint ./rileys-shop
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
A clean folder prints nothing and exits 0. Any error exits 1, and each finding
|
|
313
|
+
names its file. Warnings are printed and do not fail the command.
|
|
314
|
+
|
|
315
|
+
- Liquid in layouts, sections, snippets and pages is checked the way
|
|
316
|
+
`fluid theme lint` checks it, and its messages are the theme linter's: the
|
|
317
|
+
`{% schema %}` block and the pairing of block tags such as `{% if %}` and
|
|
318
|
+
`{% endif %}`. The theme linter's cart-code, missing-section and
|
|
319
|
+
routed-template checks are theme rules and are not run.
|
|
320
|
+
- The file rules a push enforces, with the same messages: a path outside
|
|
321
|
+
`layouts`, `sections`, `snippets`, `pages`, `assets` and `config`, a name
|
|
322
|
+
outside the allowed characters, a subfolder, a symbolic link, and an asset that
|
|
323
|
+
is binary, not css, js, json, txt or html, or empty. Every file is judged, also
|
|
324
|
+
one a pull wrote.
|
|
325
|
+
- `config/pages.json` must be valid, and lists at most 50 pages. The count is the
|
|
326
|
+
rows in the file, so a page with no file counts.
|
|
327
|
+
- `config/menus.json` must have the shape the API takes, and each menu link
|
|
328
|
+
(`page`) must name a page that `config/pages.json` lists. A folder with no
|
|
329
|
+
`config/pages.json` lists no pages.
|
|
330
|
+
- `config/access.json` must be what the file API accepts, and each problem is an
|
|
331
|
+
error: not valid JSON, a key other than `uses` or a repeated key, an entry that
|
|
332
|
+
is not a string or is listed twice, an unknown field, family or mode,
|
|
333
|
+
`session: read`, more than 64 entries, or more than 4 KiB.
|
|
334
|
+
- Warnings for a site's use of viewer data. A Liquid file that reads
|
|
335
|
+
`member.<field>` is warned whatever the declaration holds, because member data is leaving
|
|
336
|
+
member-site Liquid: read it in a script with
|
|
337
|
+
`fluid.fetch("/api/member/v2026-10/me")`, which needs `profile: read`.
|
|
338
|
+
- When `config/access.json` is valid, each `fluid.fetch(` call (also
|
|
339
|
+
`window.fluid.fetch(`) in a layout, section, snippet, page or `.js` asset is
|
|
340
|
+
checked against `src/lint/path-scopes.json`, a copy of the SDK's route table
|
|
341
|
+
that a test holds equal to the SDK's. The lint takes the path from a string
|
|
342
|
+
literal, or from a template literal whose `${...}` parts are whole path
|
|
343
|
+
segments (`` `/api/member/v2026-10/orders/${id}` ``), and the method from a
|
|
344
|
+
`method: "POST"` string literal in an object-literal second argument (`GET`
|
|
345
|
+
when there is none). It moves the path as `fluid.fetch` does: a Member API
|
|
346
|
+
path (`/api/member/v2026-10/...`) stays, and any other `/api/` path moves under
|
|
347
|
+
`/api/member-storefront/`. It ignores the query string. Then:
|
|
348
|
+
- a scope the declaration holds, or `session: read`, gives no finding;
|
|
349
|
+
- a scope the declaration lacks is warned, naming the scope;
|
|
350
|
+
- a route no site can reach is warned, with the reason;
|
|
351
|
+
- a call the lint cannot check is warned too, never skipped: a path or method
|
|
352
|
+
that is a variable or is computed, an interpolation that is not a whole
|
|
353
|
+
segment, a path outside `/api/`, or a path and method no route matches. Such
|
|
354
|
+
a call is refused at run time if its scope is not declared.
|
|
355
|
+
|
|
356
|
+
A folder with no `config/access.json` is not checked for calls, because a site
|
|
357
|
+
with no declaration is not limited.
|
|
358
|
+
|
|
359
|
+
### `fluid member-site dev [dir]`
|
|
360
|
+
|
|
361
|
+
Shows a site's draft while you edit it. It saves each change you make in a
|
|
362
|
+
pulled folder as a draft and serves the draft on a local port, rendered by the
|
|
363
|
+
server with the same renderer members get, not by a local one. Run it in the
|
|
364
|
+
folder, or pass the folder.
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
fluid member-site dev
|
|
368
|
+
fluid member-site dev ./rileys-shop --as member --port 9600
|
|
369
|
+
fluid member-site dev --allow-production
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- **It does not write production unless you say so.** A session saves the
|
|
373
|
+
folder when it starts and again on every change, and assets, removals, menus
|
|
374
|
+
and page rows are live as soon as they are written. So `dev` stops, before it
|
|
375
|
+
makes any request, when the login's API base is production, with a message that
|
|
376
|
+
names the base and the flag, and writes nothing. Pass `--allow-production` to
|
|
377
|
+
save to production anyway. A base that is not production keeps syncing without
|
|
378
|
+
it: a local one (`localhost`, a loopback address (any of `127.0.0.0/8`, or `::1`), `*.localhost`), a stage one
|
|
379
|
+
(`*.stage.fluid.app`) and a name under `.test`. Any other base, a custom or
|
|
380
|
+
unknown host included, is taken for production. `fluid theme dev` is
|
|
381
|
+
never pointed at a live theme either: it works on a development theme unless
|
|
382
|
+
`--theme` names another one, and this site has no development copy to use in
|
|
383
|
+
its place, so the choice is a flag.
|
|
384
|
+
- **It saves drafts and never publishes.** A save is `push` without
|
|
385
|
+
`--publish`: the same plan, the same refusals, the same version on every
|
|
386
|
+
write, and the same stop at a stale version (pull, then run `dev` again). It
|
|
387
|
+
never calls the publish route. What takes effect without a publish is listed
|
|
388
|
+
before each save, as for `push`: assets, removals, menus and page rows are live
|
|
389
|
+
as soon as they are written. Run `fluid member-site publish` when the draft is
|
|
390
|
+
ready.
|
|
391
|
+
- **It starts only when it can show a draft.** First it reads the site, then it
|
|
392
|
+
asks the server for the draft preview route. A server without it stops the
|
|
393
|
+
command with "Draft preview is not available on this server yet" before
|
|
394
|
+
anything is saved. It never shows the published page in its place. Then what
|
|
395
|
+
the folder holds is saved, so the page shows the folder, and a refused or stale
|
|
396
|
+
save stops the command. The folder is watched before it is read for that first
|
|
397
|
+
save, so an edit made while it runs is saved before the preview is served,
|
|
398
|
+
and none is missed.
|
|
399
|
+
- **After that**, each change is saved after a short pause, and the command
|
|
400
|
+
keeps serving when a save is refused, so you can fix the file. Reload the
|
|
401
|
+
preview to see a save. There is no live reload yet.
|
|
402
|
+
- **The preview** is `http://127.0.0.1:<port>` (`--port`, default 9500; `--port 0`
|
|
403
|
+
takes a free one). `/` is the home page and `/<slug>` another page, read
|
|
404
|
+
through `GET .../sites/:id/preview/pages(/:slug)` of the member API, or
|
|
405
|
+
`GET .../member-sites/:id/preview/pages(/:slug)` of the company API for a
|
|
406
|
+
company site. A text asset is at `/files/assets/<file>` (served to the
|
|
407
|
+
preview's own origin and the address bar only: see below), read through
|
|
408
|
+
`.../preview/files/*path`. A page links its assets at the published frame
|
|
409
|
+
address and its pages at the site's shell address, and neither opens a draft,
|
|
410
|
+
so the preview rewrites both to its own paths.
|
|
411
|
+
- **The credential stays in the CLI.** The server listens on `127.0.0.1` only.
|
|
412
|
+
The bearer is added to the request to the API by this process, and is never in
|
|
413
|
+
a URL, a page or a header sent to the browser. Nothing the browser sends
|
|
414
|
+
(cookies, its own `Authorization`) goes to the API. A request is refused
|
|
415
|
+
unless its `Host` is the local address, its `Origin` is the preview's own, and
|
|
416
|
+
it is not a cross-site request that a page could read. A preview is served
|
|
417
|
+
with the sandbox policy the frame has, `Cache-Control: private, no-store` and
|
|
418
|
+
`Referrer-Policy: no-referrer`. The sandbox gives the page an opaque origin
|
|
419
|
+
(`Origin: null`), and so does every other sandboxed page, so the origin
|
|
420
|
+
cannot say whose request it is. The preview gives each session a random token
|
|
421
|
+
and links its assets at `/files/assets/<token>/<file>`. Only `GET` of such a
|
|
422
|
+
path, as a script or a stylesheet, is let read with CORS (a module script
|
|
423
|
+
needs it), and only then is `Access-Control-Allow-Origin: null` sent. A
|
|
424
|
+
request from an opaque origin for any other path is refused. An asset path
|
|
425
|
+
without the token (`/files/assets/<file>`, or the published frame address) is
|
|
426
|
+
served only when `Sec-Fetch-Site` is `same-origin` or `none`, so a page of
|
|
427
|
+
another site that loads it as a stylesheet or script without CORS gets
|
|
428
|
+
nothing and cannot read what the CLI's login can. The preview rewrites the
|
|
429
|
+
frame addresses in a stylesheet or script to the path with the token. The token is not the
|
|
430
|
+
credential and authorizes nothing at the API.
|
|
431
|
+
- With `--as`, the login is checked as for `push`: a member's site needs a
|
|
432
|
+
member login, a company site an admin's.
|
|
433
|
+
|
|
434
|
+
## Errors
|
|
435
|
+
|
|
436
|
+
| API answer | What the command says |
|
|
437
|
+
| ---------- | ------------------------------------------------------------------------------------------------- |
|
|
438
|
+
| 404 | Member sites are not enabled for this company or the site is not yours |
|
|
439
|
+
| 401 | The login was not accepted, and which login the site needs |
|
|
440
|
+
| 403 | The API's message, and which login the site needs |
|
|
441
|
+
| 409 | The site (or the named file or page) changed on the server; on a publish, a conflict to run again |
|
|
442
|
+
| 422 | The API's message and each field error |
|
|
443
|
+
| other | The API's message and the request id |
|
|
444
|
+
|
|
445
|
+
A 404 answers the same for a company without member sites, a missing site and
|
|
446
|
+
another owner's site, so the command names the first and the last. It is never
|
|
447
|
+
retried. A read that fails with 502, 503 or 504 is retried twice. A write is
|
|
448
|
+
never retried.
|
|
449
|
+
|
|
450
|
+
## Development
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
pnpm --filter @fluid-app/fluid-cli-member-site test
|
|
454
|
+
pnpm --filter @fluid-app/fluid-cli-member-site typecheck
|
|
455
|
+
pnpm --filter @fluid-app/fluid-cli-member-site lint
|
|
456
|
+
pnpm --filter @fluid-app/fluid-cli-member-site build
|
|
457
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;cAGM,MAAA,EAAQ,WAAA"}
|