uniweb 0.84.0 → 0.86.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.
@@ -53,7 +53,7 @@ function readCliConfig() {
53
53
  /**
54
54
  * The origin the LAST `uniweb login` authenticated against — persisted on the
55
55
  * session record so subsequent verbs default to the backend you logged into
56
- * (no `--backend` per command). Sync read; null when there's no session or it
56
+ * (no backend flag per command). Sync read; null when there's no session or it
57
57
  * carries no origin (older sessions). Read directly (not via registry-auth.js)
58
58
  * to keep this module off the optional-peer / import-cycle path.
59
59
  * @returns {string|null}
@@ -91,7 +91,7 @@ function originOrNull(value) {
91
91
  * **The default backend** — where a bare `uniweb login` goes, and where a backend command
92
92
  * goes when nobody is logged in (the login it asks for is then this one).
93
93
  *
94
- * `UNIWEB_REGISTER_URL`, else `~/.uniweb/config.json` `registryApiUrl`, else
94
+ * `UNIWEB_SERVER`, else `~/.uniweb/config.json` `registryApiUrl`, else
95
95
  * https://uniweb.app. ⛔ **Never the current session** *[Diego, 2026-09-21: "the default
96
96
  * backend for login, if not specified, is uniweb.app"]* — a bare `uniweb login` means the
97
97
  * default backend, not "the one I am already on".
@@ -100,7 +100,7 @@ function originOrNull(value) {
100
100
  */
101
101
  export function getDefaultBackendOrigin() {
102
102
  return (
103
- originOrNull(process.env.UNIWEB_REGISTER_URL) ||
103
+ originOrNull(process.env.UNIWEB_SERVER) ||
104
104
  originOrNull(readCliConfig().registryApiUrl) ||
105
105
  DEFAULT_BACKEND_ORIGIN
106
106
  )
@@ -108,7 +108,7 @@ export function getDefaultBackendOrigin() {
108
108
 
109
109
  /**
110
110
  * The `uniweb login` that SWITCHES the workspace on `origin` — the caller appends `--org @x`
111
- * or `--personal` — with `--backend` unless a switch without it reaches `origin`
111
+ * or `--personal` — with `--server` unless a switch without it reaches `origin`
112
112
  * (`resolveLoginOrigin`): the backend you are logged in to, or, logged in nowhere, the
113
113
  * default backend.
114
114
  *
@@ -118,12 +118,12 @@ export function getDefaultBackendOrigin() {
118
118
  *
119
119
  * @param {string} origin - the backend the hint is about
120
120
  * @param {string} [prefix='uniweb'] - how the user runs the CLI (`getCliPrefix`)
121
- * @returns {string} e.g. `uniweb login`, or `uniweb login --backend http://localhost:8080`
121
+ * @returns {string} e.g. `uniweb login`, or `uniweb login --server http://localhost:8080`
122
122
  */
123
123
  export function loginCommand(origin, prefix = 'uniweb') {
124
124
  const o = originOrNull(origin)
125
125
  const reached = originOrNull(loggedInOrigin()) || getDefaultBackendOrigin()
126
- return o && o !== reached ? `${prefix} login --backend ${o}` : `${prefix} login`
126
+ return o && o !== reached ? `${prefix} login --server ${o}` : `${prefix} login`
127
127
  }
128
128
 
129
129
  /** The flags with which `uniweb login` SIGNS IN — a method, or a credential. */
@@ -145,7 +145,7 @@ export function isWorkspaceSwitch(args = []) {
145
145
  }
146
146
 
147
147
  /**
148
- * The backend `uniweb login` logs in to: `--backend`; else, for a workspace switch, the
148
+ * The backend `uniweb login` logs in to: `--server`; else, for a workspace switch, the
149
149
  * backend you are logged in to; else the default backend.
150
150
  *
151
151
  * ⭐ A SWITCH ACTS ON YOUR SESSION *[Diego, 2026-10-06: "`uniweb login --org @x` or
@@ -157,14 +157,14 @@ export function isWorkspaceSwitch(args = []) {
157
157
  * uniweb.app"]*. ⛔ Until 2026-10-06 a switch went to the default backend too, so on any
158
158
  * other backend it began a new login there, logging you out of the one you were on.
159
159
  *
160
- * ⛔ A mistyped `--backend` is an error, never a fallback — it would log you in, and so
160
+ * ⛔ A mistyped `--server` is an error, never a fallback — it would log you in, and so
161
161
  * point every command, somewhere you did not name.
162
162
  *
163
163
  * @param {string|null|undefined} flag - `readFlagValue`'s answer: undefined when the
164
164
  * flag is absent, null when it was given with no value
165
165
  * @param {string[]} [args] - the login's argv, to tell a switch from a sign-in
166
166
  * @returns {string}
167
- * @throws {Error} when --backend was given and is not a URL
167
+ * @throws {Error} when --server was given and is not a URL
168
168
  */
169
169
  export function resolveLoginOrigin(flag, args = []) {
170
170
  if (flag === undefined) {
@@ -172,7 +172,7 @@ export function resolveLoginOrigin(flag, args = []) {
172
172
  return session || getDefaultBackendOrigin()
173
173
  }
174
174
  const origin = originOrNull(flag)
175
- if (!origin) throw new Error(flag ? `Not a URL: ${flag}` : '--backend needs a URL')
175
+ if (!origin) throw new Error(flag ? `Not a URL: ${flag}` : '--server needs a URL')
176
176
  return origin
177
177
  }
178
178
 
@@ -180,12 +180,12 @@ export function resolveLoginOrigin(flag, args = []) {
180
180
  * **The backend a command talks to** — the base of every `/dev/*` route (`register`
181
181
  * POSTs to {origin}/dev/registry/register, and so on).
182
182
  *
183
- * `UNIWEB_REGISTER_URL` > **the backend the user is logged in to** > the default
183
+ * `UNIWEB_SERVER` > **the backend the user is logged in to** > the default
184
184
  * (`getDefaultBackendOrigin`). ⭐ Nothing talks to a backend the user is not logged in
185
185
  * to *[Diego, 2026-09-21]*: when this falls through to the default, the command's first
186
186
  * request asks for that login — so the default is where the login goes, never a
187
187
  * backend reached without one. `resolveBackendOrigin` (backend/client.js) returns this
188
- * unchanged: no command takes a `--backend`. ⛔ *Until 2026-10-05 this said "when no
188
+ * unchanged: no command takes a backend flag. ⛔ *Until 2026-10-05 this said "when no
189
189
  * `--backend` is given" and that `resolveBackendOrigin` put `--backend` on top — read as
190
190
  * a per-command flag outranking the login. The verbs lost that flag on 2026-09-21.*
191
191
  *
@@ -193,7 +193,7 @@ export function resolveLoginOrigin(flag, args = []) {
193
193
  */
194
194
  export function getRegistryApiBaseUrl() {
195
195
  return (
196
- originOrNull(process.env.UNIWEB_REGISTER_URL) ||
196
+ originOrNull(process.env.UNIWEB_SERVER) ||
197
197
  originOrNull(loggedInOrigin()) ||
198
198
  getDefaultBackendOrigin()
199
199
  )
@@ -9,14 +9,15 @@
9
9
  * Tolerable for a cosmetic flag; dangerous for any flag that aims the command or picks
10
10
  * its identity. This turns that class into one sentence.
11
11
  *
12
- * ⛔ **`--backend` and `--token` are NOT flags of these verbs** *(2026-09-21)*. Every
12
+ * ⛔ **A backend flag and `--token` are NOT flags of these verbs** *(2026-09-21)*. Every
13
13
  * backend verb goes to the backend you are logged in to, with that login's session:
14
- * switching and signing in are `uniweb login` (`--backend`, `--token`), and a script
15
- * aims one process with UNIWEB_REGISTER_URL + UNIWEB_TOKEN. Both flags predate
16
- * per-backend sessions. Passed now, each is an unknown flag — and this guard is what
17
- * makes that a loud error, with a pointer to the login, instead of a command that
18
- * quietly runs against wherever and as whoever you happen to be. `--backend` stays on
19
- * `forget`, where it SELECTS which backend's records to remove.
14
+ * switching and signing in are `uniweb login` (`--server`, `--token`), and a script
15
+ * aims one process with UNIWEB_SERVER + UNIWEB_TOKEN. Passed to a verb, each is an
16
+ * unknown flag — and this guard is what makes that a loud error, with a pointer to the
17
+ * login, instead of a command that quietly runs against wherever and as whoever you
18
+ * happen to be. `--server` stays on `forget`, where it SELECTS which backend's records
19
+ * to remove. ⛔ *The flag was `--backend` until 2026-10-07, renamed so it is not read as
20
+ * a site's `backend` service; `--backend` is refused everywhere, naming `--server`.*
20
21
  *
21
22
  * ⚠️ A wrong rejection is worse than a missed one — it breaks an invocation that
22
23
  * works — so the per-command lists must be complete, INCLUDING flags read by
@@ -93,12 +94,17 @@ const VERBS = {
93
94
  '--personal'
94
95
  ],
95
96
  /**
96
- * `forget` = remove one backend's records (`--backend <url>`), or everything a
97
+ * `forget` = remove one backend's records (`--server <url>`), or everything a
97
98
  * copied project inherited (`--all`). One of the two is required — the verb refuses
98
99
  * without it — and they exclude each other. `--non-interactive` reaches it through
99
100
  * resolveSiteDir in a workspace of several sites.
100
101
  */
101
- forget: ['--backend', '--all'],
102
+ forget: ['--server', '--all'],
103
+ /**
104
+ * `site list | unpublish | delete` — a workspace's sites. `--yes` confirms a write;
105
+ * `--json` is `list`'s porcelain; the workspace is `--org` / `--personal`.
106
+ */
107
+ site: ['--json', '--yes', '--org', '--personal'],
102
108
  status: [
103
109
  '--json', '--remote', '--dry-run',
104
110
  '--force', '--no-verify', '--no-validate', '--yes', '--org', '--personal',
@@ -191,16 +197,24 @@ export function checkFlags(verb, args = []) {
191
197
  if (!unknown.length) return null
192
198
 
193
199
  const flag = unknown[0]
194
- // ⭐ `--backend` and `--token` are not typos on these verbs — they are RETIRED
195
- // (2026-09-21), and the useful answer is what replaced them, not "run --help".
196
- if (flag === '--backend') {
200
+ // ⭐ `--backend`, `--server` and `--token` are not typos on these verbs — they aim a
201
+ // command or pick its identity, which only the login does (2026-09-21), and `--backend`
202
+ // is `--server` since 2026-10-07. The useful answer is where each went, not "run --help".
203
+ if (flag === '--backend' && verb === 'forget') {
204
+ return {
205
+ flag,
206
+ suggestion: '--server',
207
+ message: '`--backend` is now `--server`: uniweb forget --server <url>'
208
+ }
209
+ }
210
+ if (flag === '--backend' || flag === '--server') {
197
211
  return {
198
212
  flag,
199
213
  suggestion: null,
200
214
  message: [
201
- `\`uniweb ${verb}\` has no \`--backend\`: it goes to the backend you are logged in to.`,
202
- ' Switch with: uniweb login --backend <url>',
203
- ' (A script can aim one process with UNIWEB_REGISTER_URL instead.)'
215
+ `\`uniweb ${verb}\` has no \`${flag}\`: it goes to the backend you are logged in to.`,
216
+ ' Switch with: uniweb login --server <url>',
217
+ ' (A script can aim one process with UNIWEB_SERVER instead.)'
204
218
  ].join('\n')
205
219
  }
206
220
  }
@@ -210,7 +224,7 @@ export function checkFlags(verb, args = []) {
210
224
  suggestion: null,
211
225
  message: [
212
226
  `\`uniweb ${verb}\` has no \`--token\`: it uses the session of the backend you are logged in to.`,
213
- ' Sign in with a token: uniweb login --backend <url> --token <bearer>',
227
+ ' Sign in with a token: uniweb login --server <url> --token <bearer>',
214
228
  ' (A script can authenticate one process with UNIWEB_TOKEN instead.)'
215
229
  ].join('\n')
216
230
  }
@@ -116,8 +116,8 @@ export function syncedElsewhere(siteDir, origin) {
116
116
 
117
117
  /**
118
118
  * The heads-up for `syncedElsewhere`, as lines — each verb prints them with its own
119
- * reporter. Only worth saying when the verb was not TOLD where to go: a `--backend` the
120
- * user typed is already a decision.
119
+ * reporter. Only worth saying when the verb was not TOLD where to go: a backend named with
120
+ * UNIWEB_SERVER is already a decision.
121
121
  *
122
122
  * ⚖️ Worded for where the verb GOES, not for why: it is the logged-in backend, or — logged
123
123
  * in nowhere — the default one, where the login it is about to ask for will be.
@@ -130,7 +130,7 @@ export function describeSyncedElsewhere(known, origin, verb) {
130
130
  const where = known.length === 1 ? `site is on ${known[0]}` : `sites are on ${known.join(', ')}`
131
131
  return [
132
132
  `This project's ${where} — not on ${origin}, where this ${verb} goes.`,
133
- `It creates a new site there. To ${verb} to ${known.length === 1 ? 'that one' : 'one of those'} instead: uniweb login --backend <url>`
133
+ `It creates a new site there. To ${verb} to ${known.length === 1 ? 'that one' : 'one of those'} instead: uniweb login --server <url>`
134
134
  ]
135
135
  }
136
136
 
@@ -1,115 +0,0 @@
1
- /**
2
- * In-place edits to a YAML file a person wrote (`site.yml`), for a command that
3
- * must change one value and leave everything else as it was: comments, key
4
- * order, blank lines, quoting, a flow list written on one line.
5
- *
6
- * ⛔ Never load a hand-written file and dump it back. js-yaml's `dump` drops every
7
- * comment and re-flows lists and long strings — `scaffold.js` records what that
8
- * did to the templates whose comments are the point of them.
9
- *
10
- * ⭐ Every edit is VERIFIED: the edited text must parse to exactly the old data
11
- * with that one value changed. A value written in a form the line-level edit
12
- * cannot reach (a block scalar, an entry split across lines) returns null
13
- * rather than a guess, so the caller can refuse before it changes anything.
14
- */
15
-
16
- import { isDeepStrictEqual } from 'node:util'
17
- import yaml from 'js-yaml'
18
-
19
- const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
20
-
21
- /** A scalar as written after `key: `, quoted only when YAML needs it (`@scope/name` does). */
22
- const scalar = (value) => yaml.dump(value, { lineWidth: -1 }).trim()
23
-
24
- /**
25
- * Where a trailing ` # comment` begins in the text after `key:`, outside quotes,
26
- * including the whitespace before it. -1 when the line has none.
27
- */
28
- function commentStart(rest) {
29
- let quote = null
30
- for (let i = 0; i < rest.length; i++) {
31
- const ch = rest[i]
32
- if (quote) {
33
- if (ch === quote) quote = null
34
- } else if (ch === '"' || ch === "'") {
35
- quote = ch
36
- } else if (ch === '#' && i > 0 && /\s/.test(rest[i - 1])) {
37
- let start = i
38
- while (start > 0 && /\s/.test(rest[start - 1])) start--
39
- return start
40
- }
41
- }
42
- return -1
43
- }
44
-
45
- /** The edited text when it parses to `expected`, else null. */
46
- function verified(after, expected) {
47
- try {
48
- return isDeepStrictEqual(yaml.load(after) ?? {}, expected) ? after : null
49
- } catch {
50
- return null
51
- }
52
- }
53
-
54
- function load(text) {
55
- try {
56
- const data = yaml.load(text) ?? {}
57
- return data && typeof data === 'object' && !Array.isArray(data) ? data : null
58
- } catch {
59
- return null
60
- }
61
- }
62
-
63
- /**
64
- * Set a top-level, one-line scalar `key` to `value`, keeping the line's inline
65
- * comment and every other line as it was.
66
- *
67
- * @param {string} text - the file's contents
68
- * @param {string} key - a top-level key, e.g. `foundation`
69
- * @param {string} value
70
- * @returns {string|null} the new contents, or null when the edit cannot be made in place
71
- */
72
- export function setTopLevelScalar(text, key, value) {
73
- const data = load(text)
74
- if (!data) return null
75
- const match = new RegExp(`^${escapeRegex(key)}:([^\\n]*)$`, 'm').exec(text)
76
- if (!match) return null
77
- const at = commentStart(match[1])
78
- const comment = at === -1 ? '' : match[1].slice(at)
79
- const line = `${key}: ${scalar(value)}${comment}`
80
- const after = text.slice(0, match.index) + line + text.slice(match.index + match[0].length)
81
- return verified(after, { ...data, [key]: value })
82
- }
83
-
84
- /**
85
- * Replace entries of a top-level list `key` — each `from` value becomes its `to`
86
- * — wherever an entry is written on a line of its own or inside a one-line flow
87
- * list, plain or quoted. A value that only CONTAINS `from` is left alone, and so
88
- * is a comment line.
89
- *
90
- * @param {string} text - the file's contents
91
- * @param {string} key - a top-level list key, e.g. `extensions`
92
- * @param {Map<string, string>} replacements - old entry → new entry
93
- * @returns {string|null} the new contents, or null when the edit cannot be made in place
94
- */
95
- export function replaceInTopLevelList(text, key, replacements) {
96
- const data = load(text)
97
- if (!data || !Array.isArray(data[key])) return null
98
- const expected = { ...data, [key]: data[key].map((v) => (replacements.has(v) ? replacements.get(v) : v)) }
99
-
100
- const after = text
101
- .split('\n')
102
- .map((line) => {
103
- if (/^\s*#/.test(line)) return line
104
- for (const [from, to] of replacements) {
105
- const f = escapeRegex(from)
106
- line = line
107
- .replace(new RegExp(`'${f}'`, 'g'), () => `'${to.replace(/'/g, "''")}'`)
108
- .replace(new RegExp(`"${f}"`, 'g'), () => JSON.stringify(to))
109
- .replace(new RegExp(`(^|[\\s\\[,])${f}(?=$|[\\s,\\]])`, 'g'), (_, lead) => lead + to)
110
- }
111
- return line
112
- })
113
- .join('\n')
114
- return verified(after, expected)
115
- }