uniweb 1.8.1 → 1.9.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 CHANGED
@@ -334,7 +334,7 @@ cd src
334
334
  uniweb register --scope @your-org
335
335
  ```
336
336
 
337
- Real sites built on your foundation are managed in the **Uniweb apps** (web + desktop) — visual editors designed for non-technical authors. They never see git, markdown, yaml, or React. They see *your* components, with live previews and visual controls for the params you defined. The foundation becomes the editor's native vocabulary for that site: you keep creative control of the design system, they get an editor that feels custom-built for them.
337
+ Real sites built on your foundation are managed in the **Uniweb app** — a visual editor designed for non-technical authors. They never see git, markdown, yaml, or React. They see *your* components, with live previews and visual controls for the params you defined. The foundation becomes the editor's native vocabulary for that site: you keep creative control of the design system, they get an editor that feels custom-built for them.
338
338
 
339
339
  This is the best path when site content has a life independent of the foundation's release cycle — agencies, design studios, multi-client teams, or any project where content authors aren't the same people as the developers.
340
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,15 +41,15 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/content-writer": "~1.1.2",
45
- "@uniweb/core": "~1.2.2",
46
- "@uniweb/semantic-parser": "~1.8.1",
47
- "@uniweb/schemas": "~1.4.0"
44
+ "@uniweb/core": "~1.2.3",
45
+ "@uniweb/semantic-parser": "~1.9.0",
46
+ "@uniweb/schemas": "~1.4.0",
47
+ "@uniweb/content-writer": "~1.2.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/build": "~1.3.10",
51
- "@uniweb/content-reader": "~1.4.0",
52
- "@uniweb/semantic-parser": "~1.8.1"
50
+ "@uniweb/build": "~1.3.11",
51
+ "@uniweb/semantic-parser": "~1.9.0",
52
+ "@uniweb/content-reader": "~1.4.1"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -2796,8 +2796,6 @@ uniweb snapshot --compare # Several looks on one sheet; take one with it
2796
2796
  uniweb <command> --help # Per-command flags — no side effects. Prefer this over guessing.
2797
2797
  ```
2798
2798
 
2799
- **Three verbs dispatch but are deliberately not listed above.** `invite`, `handoff` and `template` are **reserved names with no implementation** — the flows they named ran against a backend the CLI no longer talks to, and the names are held so a future rebuild doesn't need a breaking change. Running any of them is not useful; their absence from this list is the answer, not an omission to fix.
2800
-
2801
2799
  ### Where a site can live
2802
2800
 
2803
2801
  **There is no lock-in, and no default you're pushed toward.** Four independent paths, and a project can change its mind:
@@ -2,7 +2,7 @@
2
2
  * BackendClient — the single front door to the Uniweb backend.
3
3
  *
4
4
  * Every CLI verb that talks to the backend (register, push, pull, clone, org,
5
- * login — and, as the consolidation proceeds, publish/deploy/invite/handoff)
5
+ * login — and, as the consolidation proceeds, publish/deploy)
6
6
  * goes through one instance of this client instead of hand-rolling
7
7
  * `fetch(… Authorization: Bearer …)` against a per-command default origin. It
8
8
  * owns the three things that were previously scattered across a dozen files:
@@ -111,6 +111,7 @@ import {
111
111
  repoOf,
112
112
  mergeInProgress,
113
113
  unmergedPaths,
114
+ ignoringRepo,
114
115
  isAncestor,
115
116
  historySyncPoint,
116
117
  madeOnto,
@@ -485,8 +486,15 @@ async function checkWorkingTree(siteDir, args) {
485
486
  const roots = siteContentRoots(siteDir)
486
487
  const written = readWritten(siteDir)
487
488
  if (filesUnder(siteDir, roots).every((f) => isPullOutput(siteDir, f, written))) return null
489
+ // A folder the repository around it ignores is outside git too — said as such, since a
490
+ // repository is right there and "not in a repository" is not what the person sees.
491
+ const ignoredBy = ignoringRepo(siteDir)
488
492
  if (isNonInteractive(args)) {
489
- error('Refusing to pull: this site is not in a git repository.')
493
+ error(
494
+ ignoredBy
495
+ ? 'Refusing to pull: this site is in a folder the repository around it ignores, so no repository holds it.'
496
+ : 'Refusing to pull: this site is not in a git repository.'
497
+ )
490
498
  note(
491
499
  'Pull rewrites pages, sections and layout from the backend and deletes what it no longer has.'
492
500
  )
@@ -494,12 +502,14 @@ async function checkWorkingTree(siteDir, args) {
494
502
  'Without version control there is no way back, and this session cannot ask.'
495
503
  )
496
504
  note(
497
- 'Re-run with --force to accept that, or put the site under git first — there a pull merges.'
505
+ ignoredBy
506
+ ? 'Re-run with --force to accept that, or give the site a repository of its own (`git init`, then commit) — there a pull merges.'
507
+ : 'Re-run with --force to accept that, or put the site under git first — there a pull merges.'
498
508
  )
499
509
  return { exitCode: 1 }
500
510
  }
501
511
  const ok = await confirm(
502
- `Pull will overwrite ${roots.length} content location(s) and this site is not in git. Continue?`
512
+ `Pull will overwrite ${roots.length} content location(s), and no git repository holds this site. Continue?`
503
513
  )
504
514
  if (!ok) {
505
515
  info('Nothing pulled.')
@@ -52,6 +52,7 @@ import {
52
52
  pullRemote,
53
53
  headProvenance
54
54
  } from '../utils/git.js'
55
+ import { ignoringRepo } from '../utils/sync-point.js'
55
56
  import { probeUnpushed } from '../backend/site-sync.js'
56
57
  import { resolveBackendOrigin } from '../backend/client.js'
57
58
  import { readBackendState } from '@uniweb/build/uwx'
@@ -124,13 +125,18 @@ export async function refresh(args = [], deps = {}) {
124
125
  // when they are not.
125
126
  const consulted = []
126
127
  const skipped = []
128
+ // Said at the end — and before stopping on a failure, which can follow a step skipped.
129
+ const tell = () => {
130
+ if (consulted.length) say.dim(`Checked: ${consulted.join(', ')}`)
131
+ for (const s of skipped) say.dim(`Skipped: ${s}`)
132
+ }
127
133
  let conflicts = 0
128
134
 
129
135
  // ── 1. the git remote ─────────────────────────────────────────────────────
130
136
  if (skipGit) {
131
137
  skipped.push('git (--no-git)')
132
138
  } else if (!isGitRepo(siteDir)) {
133
- skipped.push('git (not a repository)')
139
+ skipped.push(ignoringRepo(siteDir) ? 'git (the repository around this folder ignores it)' : 'git (not a repository)')
134
140
  } else if (!hasRemote(siteDir)) {
135
141
  skipped.push('git (no remote configured)')
136
142
  } else {
@@ -173,6 +179,7 @@ export async function refresh(args = [], deps = {}) {
173
179
  // Failed for a reason other than conflicts — say so plainly rather than
174
180
  // reporting a clean refresh over a lane that never ran.
175
181
  say.err('Could not merge the backend content.')
182
+ tell()
176
183
  return { exitCode: res.exitCode }
177
184
  }
178
185
  consulted.push('backend')
@@ -180,8 +187,7 @@ export async function refresh(args = [], deps = {}) {
180
187
 
181
188
  // ── 3. where that leaves you ──────────────────────────────────────────────
182
189
  console.log('')
183
- if (consulted.length) say.dim(`Checked: ${consulted.join(', ')}`)
184
- for (const s of skipped) say.dim(`Skipped: ${s}`)
190
+ tell()
185
191
 
186
192
  const git = headProvenance(siteDir)
187
193
  if (git)
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-17T22:35:38.030Z",
3
+ "generatedAt": "2026-10-18T05:12:16.689Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "1.5.0"
7
7
  },
8
8
  "@uniweb/build": {
9
- "version": "1.3.10"
9
+ "version": "1.3.11"
10
10
  },
11
11
  "@uniweb/content-reader": {
12
- "version": "1.4.0"
12
+ "version": "1.4.1"
13
13
  },
14
14
  "@uniweb/content-writer": {
15
- "version": "1.1.2"
15
+ "version": "1.2.0"
16
16
  },
17
17
  "@uniweb/core": {
18
- "version": "1.2.2"
18
+ "version": "1.2.3"
19
19
  },
20
20
  "@uniweb/frame-bridge": {
21
21
  "version": "1.0.2"
@@ -24,7 +24,7 @@
24
24
  "version": "1.0.3"
25
25
  },
26
26
  "@uniweb/kit": {
27
- "version": "1.2.5"
27
+ "version": "1.3.0"
28
28
  },
29
29
  "@uniweb/loom": {
30
30
  "version": "1.1.0"
@@ -33,10 +33,10 @@
33
33
  "version": "1.1.1"
34
34
  },
35
35
  "@uniweb/projections": {
36
- "version": "1.1.1"
36
+ "version": "1.1.2"
37
37
  },
38
38
  "@uniweb/runtime": {
39
- "version": "1.1.4"
39
+ "version": "1.1.5"
40
40
  },
41
41
  "@uniweb/scene": {
42
42
  "version": "1.0.1"
@@ -48,7 +48,7 @@
48
48
  "version": "1.1.0"
49
49
  },
50
50
  "@uniweb/semantic-parser": {
51
- "version": "1.8.1"
51
+ "version": "1.9.0"
52
52
  },
53
53
  "@uniweb/snapshot": {
54
54
  "version": "1.1.0"
@@ -60,7 +60,7 @@
60
60
  "version": "1.2.0"
61
61
  },
62
62
  "@uniweb/unipress": {
63
- "version": "1.1.5"
63
+ "version": "1.1.6"
64
64
  }
65
65
  },
66
66
  "templates": {
package/src/index.js CHANGED
@@ -33,8 +33,6 @@ import prompts from 'prompts'
33
33
  // Same pattern as `build` and `docs`.
34
34
  import { i18n } from './commands/i18n.js'
35
35
  import { inspect } from './commands/inspect.js'
36
- import { invite } from './commands/invite.js'
37
- import { handoff } from './commands/handoff.js'
38
36
  import { update } from './commands/update.js'
39
37
  import { clone } from './commands/clone.js'
40
38
  import { template } from './commands/template.js'
@@ -1010,18 +1008,6 @@ async function main() {
1010
1008
  return
1011
1009
  }
1012
1010
 
1013
- // Handle invite command
1014
- if (command === 'invite') {
1015
- await invite(args.slice(1))
1016
- return
1017
- }
1018
-
1019
- // Handle handoff command
1020
- if (command === 'handoff') {
1021
- await handoff(args.slice(1))
1022
- return
1023
- }
1024
-
1025
1011
  // Handle families command — the standard section families this CLI ships with
1026
1012
  if (command === 'families') {
1027
1013
  const { families } = await import('./commands/families.js')
@@ -1926,18 +1912,6 @@ Record files keep their \`$uuid\`: it is the record's own id, not a backend's.
1926
1912
 
1927
1913
  To duplicate a project:
1928
1914
  cp -r my-site my-site-2 && cd my-site-2 && uniweb forget --all
1929
- `,
1930
- invite: `
1931
- ${colors.cyan}${colors.bright}uniweb invite${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1932
-
1933
- The foundation client-invite flow was retired with the legacy backend.
1934
- Invite clients to your foundation from the Uniweb app for now.
1935
- `,
1936
- handoff: `
1937
- ${colors.cyan}${colors.bright}uniweb handoff${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1938
-
1939
- The site-handoff flow was retired with the legacy backend.
1940
- Manage client sites from the Uniweb app for now.
1941
1915
  `,
1942
1916
  families: `
1943
1917
  ${colors.cyan}${colors.bright}uniweb families${colors.reset} ${colors.dim}— The standard section families${colors.reset}
@@ -14,9 +14,9 @@
14
14
  * foundation — `uniweb dev` with `foundation:` naming a sibling package. There,
15
15
  * the site imports `#foundation`, which `build/src/site/config.js` aliases to
16
16
  * the foundation's PACKAGE NAME, so Vite resolves it through node_modules. A
17
- * linked site (registry ref or URL) never takes that path, and neither does the
18
- * desktop app, unipress, or the edge — they load a built `dist/entry.js`, or
19
- * read a content folder directly. The check stays silent for all of them
17
+ * linked site (registry ref or URL) never takes that path, and neither does
18
+ * unipress or the edge — they load a built `dist/entry.js`, or read a content
19
+ * folder directly. The check stays silent for all of them
20
20
  * because there is no node_modules entry to look at.
21
21
  *
22
22
  * What goes wrong in that one modality: a `file:` dependency can be satisfied
@@ -107,6 +107,21 @@ export function repoOf(siteDir) {
107
107
  return { top, prefix, path: prefix ? prefix.replace(/\/$/, '') : '.', gitDir, head }
108
108
  }
109
109
 
110
+ /**
111
+ * The top of the repository around `dir` when that repository ignores it — and so holds none
112
+ * of it — or null. For what is said to a person: "not in a repository" is not what they see
113
+ * when one is right there.
114
+ */
115
+ export function ignoringRepo(dir) {
116
+ try {
117
+ const top = gitText(dir, ['rev-parse', '--show-toplevel']).trim()
118
+ const prefix = gitText(dir, ['rev-parse', '--show-prefix']).trim()
119
+ return top && prefix && ignoredHere(dir) ? top : null
120
+ } catch {
121
+ return null
122
+ }
123
+ }
124
+
110
125
  /** Does the repository around `dir` ignore it? `git check-ignore` exits 0 for yes, 1 for no. */
111
126
  function ignoredHere(dir) {
112
127
  try {
@@ -1,21 +0,0 @@
1
- /**
2
- * handoff — RESERVED (not available on the new backend yet).
3
- *
4
- * The "create a site and hand it off to a client" flow ran against the legacy
5
- * platform (the PHP backend + Cloudflare Worker) the CLI no longer talks to. A
6
- * new-backend CLI equivalent isn't built; the command name is kept reserved so a
7
- * future rebuild can claim it without a breaking change.
8
- */
9
-
10
- export async function handoff() {
11
- console.error(
12
- "\x1b[31m✗\x1b[0m `uniweb handoff` isn't available on the new backend yet."
13
- )
14
- console.error(
15
- ' The legacy site-handoff flow was retired with the PHP backend.'
16
- )
17
- console.error(' Manage client sites from the Uniweb app for now.')
18
- process.exit(1)
19
- }
20
-
21
- export default handoff
@@ -1,24 +0,0 @@
1
- /**
2
- * invite — RESERVED (not available on the new backend yet).
3
- *
4
- * The foundation client-invite flow ran against the legacy platform (the PHP
5
- * backend + Cloudflare Worker) the CLI no longer talks to. Client onboarding is
6
- * handled in the Uniweb app for now; a new-backend CLI equivalent isn't built.
7
- * The command name is kept reserved so a future rebuild can claim it without a
8
- * breaking change.
9
- */
10
-
11
- export async function invite() {
12
- console.error(
13
- "\x1b[31m✗\x1b[0m `uniweb invite` isn't available on the new backend yet."
14
- )
15
- console.error(
16
- ' The legacy client-invite flow was retired with the PHP backend.'
17
- )
18
- console.error(
19
- ' Invite clients to your foundation from the Uniweb app for now.'
20
- )
21
- process.exit(1)
22
- }
23
-
24
- export default invite