uniweb 0.83.0 → 0.85.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/package.json +5 -5
- package/partials/agents.md +99 -51
- package/src/backend/service-request.js +146 -173
- package/src/backend/site-sync.js +154 -47
- package/src/commands/doctor.js +32 -36
- package/src/commands/publish.js +25 -155
- package/src/commands/pull.js +13 -2
- package/src/commands/push.js +6 -1
- package/src/framework-index.json +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
44
|
"@uniweb/content-writer": "^0.3.4",
|
|
45
|
-
"@uniweb/runtime": "^0.29.
|
|
46
|
-
"@uniweb/kit": "^0.19.
|
|
45
|
+
"@uniweb/runtime": "^0.29.2",
|
|
46
|
+
"@uniweb/kit": "^0.19.18",
|
|
47
47
|
"@uniweb/schemas": "^0.12.0",
|
|
48
|
-
"@uniweb/core": "^0.37.
|
|
48
|
+
"@uniweb/core": "^0.37.2",
|
|
49
49
|
"@uniweb/semantic-parser": "^1.5.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@uniweb/build": "^0.76.1",
|
|
53
52
|
"@uniweb/content-reader": "^1.2.8",
|
|
53
|
+
"@uniweb/build": "^0.78.0",
|
|
54
54
|
"@uniweb/semantic-parser": "^1.5.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
package/partials/agents.md
CHANGED
|
@@ -1962,15 +1962,15 @@ When an external query is enough and when a transport is the answer: `developmen
|
|
|
1962
1962
|
|
|
1963
1963
|
Full model: `reference/data-fetching.md`. Where-object format with examples: `authoring/predicates.md`.
|
|
1964
1964
|
|
|
1965
|
-
### Search (`search
|
|
1965
|
+
### Search (`services.search`)
|
|
1966
1966
|
|
|
1967
|
-
Search follows the same arrangement as `fetcher:` — the **site** declares where results come from, and a search UI reads them the same way regardless. Never hardcode a search endpoint in a component; that couples the foundation to one host.
|
|
1967
|
+
Search follows the same arrangement as `fetcher:` — the **site** declares where results come from, and a search UI reads them the same way regardless. Never hardcode a search endpoint in a component; that couples the foundation to one host. Search is on by default; its entry under `services:` (see *Services*, below) turns it off, sets options, or — on a site you publish — asks the host for it:
|
|
1968
1968
|
|
|
1969
1969
|
```yaml
|
|
1970
1970
|
# site.yml
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1971
|
+
services:
|
|
1972
|
+
search:
|
|
1973
|
+
provider: index # default — download an index, match in the browser
|
|
1974
1974
|
```
|
|
1975
1975
|
|
|
1976
1976
|
| Provider | Answers with | Trade-off |
|
|
@@ -1980,12 +1980,13 @@ search:
|
|
|
1980
1980
|
| *any other name* | A foundation-supplied search transport | Fully open — Typesense, Meilisearch, Pagefind, a vendor API |
|
|
1981
1981
|
|
|
1982
1982
|
```yaml
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1983
|
+
services:
|
|
1984
|
+
search:
|
|
1985
|
+
provider: endpoint
|
|
1986
|
+
endpoint: _search # REQUIRED — there is no default
|
|
1986
1987
|
```
|
|
1987
1988
|
|
|
1988
|
-
`endpoint:` is **required** with `provider: endpoint`; omit it and the provider refuses the query rather than guessing a path. It resolves against the site's base path — `/` → `/_search`, `base: /docs/` → `/docs/_search`, a subpath-served site follows its subpath. An absolute `https://…` URL points at another origin. A host that serves the site may offer search itself,
|
|
1989
|
+
`endpoint:` is **required** with `provider: endpoint`; omit it and the provider refuses the query rather than guessing a path. It resolves against the site's base path — `/` → `/_search`, `base: /docs/` → `/docs/_search`, a subpath-served site follows its subpath. An absolute `https://…` URL points at another origin. A host that serves the site may offer search itself: `search: true` asks for it, and the host supplies the address. An endpoint of your own asks the host to leave its search off, so yours answers.
|
|
1989
1990
|
|
|
1990
1991
|
**Results have one shape, whatever the provider.** Always present: `id`, `type`, `route`, `href`, `title`, `pageTitle`, `excerpt`, `snippetHtml`. Provider-optional (`null` when absent): `sectionId`, `anchor`, `description`, `component`, `snippetText`, `matches`, `group`, `item`. `type` is `page`, `section` or `record`; on a record hit, `item` holds the record's fields and `group` names the set it came from. Whether an optional field arrives is a deployment fact, not a content fact — the same site yields `item` from a server provider and `null` from the local index — so guard them: `result.item?.image`.
|
|
1991
1992
|
|
|
@@ -2002,26 +2003,27 @@ const { results, isLoading, query } = useSearch(website) // `query` is a funct
|
|
|
2002
2003
|
|
|
2003
2004
|
Full reference: `authoring/search.md`.
|
|
2004
2005
|
|
|
2005
|
-
### Forms (`submit
|
|
2006
|
+
### Forms (`services.submit`)
|
|
2006
2007
|
|
|
2007
2008
|
Drawing a form is a foundation's job; delivering what a visitor typed needs a
|
|
2008
2009
|
server. Where that server is comes from the **site or its host** — never from a
|
|
2009
|
-
section type, same arrangement as `fetcher:` and
|
|
2010
|
+
section type, same arrangement as `fetcher:` and search.
|
|
2010
2011
|
|
|
2011
2012
|
A form gets its destination from the first of these that applies:
|
|
2012
2013
|
|
|
2013
|
-
1. **One the host supplies** — `services.submit` in the
|
|
2014
|
-
host handles submissions, that is the destination
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
3. **Neither** — there is no destination
|
|
2020
|
-
|
|
2014
|
+
1. **One the host supplies** — `config.services.submit` in the payload it serves. Where the
|
|
2015
|
+
host handles submissions, that is the destination. A site published to Uniweb
|
|
2016
|
+
Cloud asks for it with `submit: true` under `services:` and names no address.
|
|
2017
|
+
2. **An address of the site's own** — `submit: <url>` under `services:`, for a
|
|
2018
|
+
static site or a host that does not handle submissions. On a host that does, it
|
|
2019
|
+
asks the host to leave its own off, so this one answers.
|
|
2020
|
+
3. **Neither** — there is no destination: render no form, or fall back to contact
|
|
2021
|
+
details the site already carries.
|
|
2021
2022
|
|
|
2022
|
-
That is the general arrangement, not a forms-only one. A host
|
|
2023
|
-
|
|
2024
|
-
by the same rule — the host's offer, then
|
|
2023
|
+
That is the general arrangement, not a forms-only one. A host states everything
|
|
2024
|
+
it offers in the served payload's `config.services`, keyed by name, and every service
|
|
2025
|
+
resolves by the same rule — the host's offer, then the site's own address, then
|
|
2026
|
+
neither.
|
|
2025
2027
|
|
|
2026
2028
|
⭐ **Before you render UI for a service, ask whether the site has it** — one predicate per service,
|
|
2027
2029
|
no arguments: `isSearchEnabled()`, `isSubmitEnabled()`, `isApiEnabled()`, `isAssistantEnabled()`,
|
|
@@ -2116,10 +2118,15 @@ wherever tracking is configured, with no help from your components. List
|
|
|
2116
2118
|
leaving it out does not switch the baseline off.
|
|
2117
2119
|
|
|
2118
2120
|
```yaml
|
|
2119
|
-
# site.yml
|
|
2120
|
-
|
|
2121
|
-
submit:
|
|
2122
|
-
|
|
2121
|
+
# site.yml
|
|
2122
|
+
services:
|
|
2123
|
+
submit: true # your host's form handling (Uniweb Cloud)
|
|
2124
|
+
```
|
|
2125
|
+
|
|
2126
|
+
```yaml
|
|
2127
|
+
# site.yml — when YOU are providing the endpoint: `uniweb export`, most `deploy --host` targets
|
|
2128
|
+
services:
|
|
2129
|
+
submit: https://forms.example.com/intake # or /forms, base-relative like search's endpoint
|
|
2123
2130
|
```
|
|
2124
2131
|
|
|
2125
2132
|
```jsx
|
|
@@ -2138,12 +2145,12 @@ if (!canSubmit) return null // nowhere to send — render no form, or fall ba
|
|
|
2138
2145
|
```
|
|
2139
2146
|
|
|
2140
2147
|
> **The framework never invents an endpoint — but a host may supply one.** Don't
|
|
2141
|
-
>
|
|
2142
|
-
>
|
|
2143
|
-
> one hosting.
|
|
2148
|
+
> write an address reflexively: on Uniweb Cloud an address asks the host to turn its
|
|
2149
|
+
> own form handling off — `submit: true` is the whole declaration there. Reach for an
|
|
2150
|
+
> address when you are the one hosting.
|
|
2144
2151
|
>
|
|
2145
|
-
> `canSubmit` is false only when neither
|
|
2146
|
-
> destination. **Check it when you render, not only on the button press** — a
|
|
2152
|
+
> `canSubmit` is false only when neither the host nor an address of the site's own
|
|
2153
|
+
> supplies a destination. **Check it when you render, not only on the button press** — a
|
|
2147
2154
|
> form nobody can send should not be on the page at all. A read that 404s
|
|
2148
2155
|
> degrades to `[]` and the page still renders; a write that 404s loses what a
|
|
2149
2156
|
> person typed, so it gets no silent fallback.
|
|
@@ -2197,7 +2204,7 @@ from. `values` keeps the `File` so your input can show its selection.
|
|
|
2197
2204
|
|
|
2198
2205
|
Full reference: `development/receiving-form-submissions.md`.
|
|
2199
2206
|
|
|
2200
|
-
### Tracking (`tracking
|
|
2207
|
+
### Tracking (`services.tracking`)
|
|
2201
2208
|
|
|
2202
2209
|
A site may declare one **tracking destination**, and everything worth counting
|
|
2203
2210
|
goes there as an event on a single stream. A page visit is just the event the
|
|
@@ -2205,23 +2212,28 @@ runtime emits by itself.
|
|
|
2205
2212
|
|
|
2206
2213
|
```yaml
|
|
2207
2214
|
# site.yml — your own collector, on any host
|
|
2208
|
-
|
|
2215
|
+
services:
|
|
2216
|
+
tracking: https://collector.example.com/events
|
|
2217
|
+
```
|
|
2209
2218
|
|
|
2219
|
+
```yaml
|
|
2210
2220
|
# or, when it needs more than an address
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2221
|
+
services:
|
|
2222
|
+
tracking:
|
|
2223
|
+
endpoint: /collect
|
|
2224
|
+
consent: required
|
|
2214
2225
|
```
|
|
2215
2226
|
|
|
2216
|
-
A host may also supply one
|
|
2217
|
-
applies: the host's, then yours, then neither.
|
|
2227
|
+
A host may also supply one — `tracking: true` asks for it on a site you publish —
|
|
2228
|
+
and the usual precedence applies: the host's, then yours, then neither. An
|
|
2229
|
+
endpoint of your own asks the host to leave its collector off, so yours answers.
|
|
2218
2230
|
|
|
2219
2231
|
⚠️ **The endpoint has to accept the framework's own format** — a batched
|
|
2220
2232
|
`{ "events": [ … ] }` POST, documented in `reference/site-configuration.md`. It
|
|
2221
2233
|
is not a third-party analytics product's public API, which expects that
|
|
2222
2234
|
product's own shape.
|
|
2223
2235
|
|
|
2224
|
-
A site may also name a vendor's own script under `tracking.scripts`, which the
|
|
2236
|
+
A site may also name a vendor's own script under `services.tracking.scripts`, which the
|
|
2225
2237
|
runtime loads once, after consent when a gate is declared, and never in a frame
|
|
2226
2238
|
or during prerender. That is a **separate path with no connection to the stream
|
|
2227
2239
|
below** — the vendor measures its own way, and nothing you `track()` reaches it.
|
|
@@ -2271,21 +2283,24 @@ Narrow or widen that with `emit`:
|
|
|
2271
2283
|
|
|
2272
2284
|
```yaml
|
|
2273
2285
|
# site.yml — your own collector
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2286
|
+
services:
|
|
2287
|
+
tracking:
|
|
2288
|
+
endpoint: https://collector.example.com/events
|
|
2289
|
+
emit: standard # minimal | standard | all — or a list of event names
|
|
2290
|
+
```
|
|
2277
2291
|
|
|
2292
|
+
```yaml
|
|
2278
2293
|
# site.yml — a host that supplies the collector: say what to send, not where
|
|
2279
|
-
|
|
2280
|
-
|
|
2294
|
+
services:
|
|
2295
|
+
tracking:
|
|
2296
|
+
emit: minimal
|
|
2281
2297
|
```
|
|
2282
2298
|
|
|
2283
2299
|
⭐ **`emit` needs no endpoint of its own.** Where a host provides one, the site
|
|
2284
2300
|
declares only what it wants sent and the address comes from the host. The two
|
|
2285
2301
|
are read key by key, so naming `emit` alone overrides nothing else the host
|
|
2286
|
-
declared. An `endpoint:` of your own
|
|
2287
|
-
|
|
2288
|
-
one, the host's is used.
|
|
2302
|
+
declared. An `endpoint:` of your own means you collect yourself, and asks the host
|
|
2303
|
+
to leave its collector off.
|
|
2289
2304
|
|
|
2290
2305
|
`minimal` is `page_view` alone. `standard` is the default when the collector is
|
|
2291
2306
|
your own. `all` is a standing yes, so an event added in a later framework
|
|
@@ -2540,8 +2555,9 @@ permission model and a CSS one.
|
|
|
2540
2555
|
You do not need a live backend to build against one. In `site.yml`:
|
|
2541
2556
|
|
|
2542
2557
|
```yaml
|
|
2543
|
-
|
|
2544
|
-
|
|
2558
|
+
services:
|
|
2559
|
+
api: true # ask your host for an app backend when you publish
|
|
2560
|
+
$devApi: ./mock/api.js # what answers it in `uniweb dev`; `$` keys are never published
|
|
2545
2561
|
```
|
|
2546
2562
|
|
|
2547
2563
|
```js
|
|
@@ -2550,8 +2566,10 @@ import { createMockBackend } from '@uniweb/api/mock'
|
|
|
2550
2566
|
export default createMockBackend({ seed }).fetch
|
|
2551
2567
|
```
|
|
2552
2568
|
|
|
2553
|
-
`uniweb dev`
|
|
2554
|
-
|
|
2569
|
+
`uniweb dev` answers your site's `api` service with it, at an address the dev server
|
|
2570
|
+
supplies on its own origin, so cookies behave exactly as they will in production. Write
|
|
2571
|
+
no address of your own for it: under `services:` an address means a backend you run, and
|
|
2572
|
+
asks your host to leave its own off. It **enforces** who
|
|
2555
2573
|
may edit an entry and `append_only`, so a permission you are relying on fails on your
|
|
2556
2574
|
machine rather than in front of a user. State is in memory; restart to reset.
|
|
2557
2575
|
|
|
@@ -2657,6 +2675,36 @@ Foundations have their own free path too: `uniweb add ci --target foundation` pu
|
|
|
2657
2675
|
|
|
2658
2676
|
**`sync.json` says which site this is, on each backend.** The first push to a backend records what that backend assigned — the site's id and owner, the ids of its records and uploaded files — in `sync.json` beside `site.yml`. Commit it; never edit it. **To make a new site from a copy of a project, run `uniweb forget --all` in the copy before its first push** — the copy carries the original's `sync.json`, so otherwise its push updates the original's site. When two projects in one workspace hold the same site, a push or publish from either is refused until that is done. `uniweb forget --backend <url>` removes just one backend's records, such as a scratch server's. A record file's `$uuid` is its own id and stays in both cases. **`push`, `pull` and `publish` go to the backend you are logged in to** — the last `uniweb login --backend <url>` — and never to one you are not logged in to. A bare `uniweb login` logs in to https://uniweb.app; for any other backend, name it — logging in is how you switch, and the backend commands take no `--backend` of their own. When the project has no site on that backend but has one elsewhere, a push says so before creating a new one.
|
|
2659
2677
|
|
|
2678
|
+
**`services:` in `site.yml` is where a site says which services it uses** — site search, form
|
|
2679
|
+
handling, analytics, an assistant, accounts — one entry per service:
|
|
2680
|
+
|
|
2681
|
+
```yaml
|
|
2682
|
+
services:
|
|
2683
|
+
search: true # on — your host's, or the built-in index
|
|
2684
|
+
submit: false # off
|
|
2685
|
+
tracking: https://collector.example.com/events # a provider you bring
|
|
2686
|
+
api: # on, with the service's own settings
|
|
2687
|
+
grade: pro
|
|
2688
|
+
```
|
|
2689
|
+
|
|
2690
|
+
An entry is `true`, `false`, an address of your own (a string, or `endpoint:` in a map), or a map
|
|
2691
|
+
of options. On a site you push or publish it is also **what you ask your host for**: `true` asks for
|
|
2692
|
+
its service, `false` asks it to turn its service off, and an address asks it to leave its own off so
|
|
2693
|
+
yours answers. **On a site your host publishes, a service is off unless `services:` asks for it** —
|
|
2694
|
+
list the ones the site uses (the templates list theirs). `records: true` asks for your records to be
|
|
2695
|
+
delivered live to the published pages: a query whose records have a data schema shows nothing there
|
|
2696
|
+
without it, and `publish` says so; a static build reads records from files and ignores it. An entry
|
|
2697
|
+
says everything about its service: a setting you remove from it is removed on the next push, and
|
|
2698
|
+
removing the entry switches the service off. `uniweb push` and `uniweb publish` send each service
|
|
2699
|
+
`site.yml` lists; a service the site has that this copy has never seen (turned on in the app since
|
|
2700
|
+
your last pull) is left as it is, and one changed both in the file and on the site since your last
|
|
2701
|
+
pull stops the push, naming it — `uniweb pull --merge`, then push. `uniweb pull` writes what the site
|
|
2702
|
+
has into `services:` (an off service with no settings only where the file already names it) — pull
|
|
2703
|
+
before you edit. A change that needs payment is settled in the app: `publish` opens it. **Everything in an entry but a
|
|
2704
|
+
credential is public** — it is built into the site, except `api`'s settings, which only your host
|
|
2705
|
+
reads; a key or token is set in the app. ⛔ The top-level `search:` / `submit:` / `assistant:` /
|
|
2706
|
+
`tracking:` / `api:` keys are retired: the build stops on them and says where each one moves.
|
|
2707
|
+
|
|
2660
2708
|
**The Cloud also provides a real backend for structured data:** a database for every registered data schema, and a CMS that edits both static page content and dynamic data entities typed by those schemas. That's the piece that makes it viable for teams and client work — the client manages records, not markdown files.
|
|
2661
2709
|
|
|
2662
2710
|
Either side can publish. Nothing about this changes how you build: the same foundation and the same site run under `uniweb dev`, `uniweb export`, or a CI deploy with no account at all.
|