uniweb 0.85.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.
@@ -33,7 +33,7 @@ import { createRequire } from 'node:module'
33
33
  import { isAbsolute, join, relative, resolve, sep } from 'node:path'
34
34
  import { pathToFileURL } from 'node:url'
35
35
  import yaml from 'js-yaml'
36
- import { upsertYamlScalar } from '@uniweb/build/uwx'
36
+ import { writeSiteConfig } from '@uniweb/build/uwx'
37
37
 
38
38
  import { didYouMean } from '../utils/args.js'
39
39
  import { humanBytes } from '../utils/bytes.js'
@@ -431,14 +431,23 @@ export async function snapshot(args = []) {
431
431
  return
432
432
  }
433
433
  const current = siteYml.data.preview
434
+ // Edited in place, its comments kept (`writeSiteConfig`) — and not at all when site.yml
435
+ // does not parse: that is said, and the file left as the author has it.
436
+ const setPreview = () => {
437
+ try {
438
+ writeSiteConfig(siteDir, { preview: value })
439
+ return true
440
+ } catch (err) {
441
+ console.log(` ${YELLOW}site.yml not changed:${RESET} ${DIM}${err.message}${RESET}`)
442
+ return false
443
+ }
444
+ }
434
445
  switch (previewDecision(current, value)) {
435
446
  case 'set':
436
- upsertYamlScalar(siteYml.file, 'preview', value)
437
- console.log(` site.yml: ${CYAN}preview: ${value}${RESET}`)
447
+ if (setPreview()) console.log(` site.yml: ${CYAN}preview: ${value}${RESET}`)
438
448
  break
439
449
  case 'replace':
440
- upsertYamlScalar(siteYml.file, 'preview', value)
441
- console.log(` site.yml: ${CYAN}preview: ${value}${RESET} ${DIM}(replaces the app-generated preview)${RESET}`)
450
+ if (setPreview()) console.log(` site.yml: ${CYAN}preview: ${value}${RESET} ${DIM}(replaces the app-generated preview)${RESET}`)
442
451
  break
443
452
  case 'unchanged':
444
453
  console.log(` ${DIM}site.yml already has preview: ${value}${RESET}`)
@@ -243,7 +243,7 @@ export async function status(args = []) {
243
243
  if (siteState?.state === 'gone') {
244
244
  say.warn(`The backend has no site ${uuid} — it was deleted there, or the backend was rebuilt.`)
245
245
  say.dim(
246
- `To push this as a new site: uniweb forget --backend ${probeBackend}, then uniweb push.`
246
+ `To push this as a new site: uniweb forget --server ${probeBackend}, then uniweb push.`
247
247
  )
248
248
  } else if (siteState?.state === 'unknown') {
249
249
  say.dim(
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-07T11:39:54.164Z",
3
+ "generatedAt": "2026-10-07T18:16:06.785Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
- "version": "0.6.10",
6
+ "version": "0.7.0",
7
7
  "path": "framework/api",
8
8
  "deps": [
9
9
  "@uniweb/core"
10
10
  ]
11
11
  },
12
12
  "@uniweb/build": {
13
- "version": "0.78.0",
13
+ "version": "0.79.0",
14
14
  "path": "framework/build",
15
15
  "deps": [
16
16
  "@uniweb/content-reader",
@@ -35,7 +35,7 @@
35
35
  "deps": []
36
36
  },
37
37
  "@uniweb/core": {
38
- "version": "0.37.2",
38
+ "version": "0.38.0",
39
39
  "path": "framework/core",
40
40
  "deps": [
41
41
  "@uniweb/semantic-parser",
@@ -48,14 +48,14 @@
48
48
  "deps": []
49
49
  },
50
50
  "@uniweb/icons": {
51
- "version": "0.4.28",
51
+ "version": "0.4.29",
52
52
  "path": "framework/icons",
53
53
  "deps": [
54
54
  "@uniweb/core"
55
55
  ]
56
56
  },
57
57
  "@uniweb/kit": {
58
- "version": "0.19.18",
58
+ "version": "0.20.0",
59
59
  "path": "framework/kit",
60
60
  "deps": [
61
61
  "@uniweb/core",
@@ -76,7 +76,7 @@
76
76
  "deps": []
77
77
  },
78
78
  "@uniweb/projections": {
79
- "version": "0.7.14",
79
+ "version": "0.7.15",
80
80
  "path": "framework/projections",
81
81
  "deps": [
82
82
  "@uniweb/content-writer",
@@ -84,7 +84,7 @@
84
84
  ]
85
85
  },
86
86
  "@uniweb/runtime": {
87
- "version": "0.29.2",
87
+ "version": "0.29.3",
88
88
  "path": "framework/runtime",
89
89
  "deps": [
90
90
  "@uniweb/core",
@@ -97,7 +97,7 @@
97
97
  "deps": []
98
98
  },
99
99
  "@uniweb/schemas": {
100
- "version": "0.12.0",
100
+ "version": "0.13.0",
101
101
  "path": "framework/schemas",
102
102
  "deps": []
103
103
  },
@@ -117,7 +117,7 @@
117
117
  "deps": []
118
118
  },
119
119
  "@uniweb/templates": {
120
- "version": "0.18.0",
120
+ "version": "0.18.1",
121
121
  "path": "framework/templates",
122
122
  "deps": []
123
123
  },
@@ -127,7 +127,7 @@
127
127
  "deps": []
128
128
  },
129
129
  "@uniweb/unipress": {
130
- "version": "0.10.22",
130
+ "version": "0.10.23",
131
131
  "path": "framework/unipress",
132
132
  "deps": [
133
133
  "@uniweb/build",
package/src/index.js CHANGED
@@ -645,6 +645,15 @@ async function main() {
645
645
  const command = args[0]
646
646
  const pm = detectPackageManager()
647
647
 
648
+ // ⛔ THE VARIABLE THAT AIMS A PROCESS IS UNIWEB_SERVER — UNIWEB_REGISTER_URL until 2026-10-07.
649
+ // Set ALONE, the old name is refused rather than ignored: ignored, a script that meant a
650
+ // local or staging backend would go to the default one, uniweb.app, and say nothing. Set
651
+ // beside the new one it is ignored, so a script can carry both while CLIs of both ages run it.
652
+ if (process.env.UNIWEB_REGISTER_URL && !process.env.UNIWEB_SERVER) {
653
+ error('UNIWEB_REGISTER_URL is now UNIWEB_SERVER — rename it, and the command goes to the same backend.')
654
+ process.exit(2)
655
+ }
656
+
648
657
  // Handle --version / -v
649
658
  //
650
659
  // Output convention: the version goes to stdout (parseable, scriptable —
@@ -915,13 +924,13 @@ async function main() {
915
924
  }
916
925
 
917
926
  // Handle login command — the backend (username/password · paste a token ·
918
- // --token <bearer>). ⭐ `--backend <url>` names it; without the flag it is the DEFAULT
919
- // backend — UNIWEB_REGISTER_URL, else ~/.uniweb/config.json, else https://uniweb.app —
927
+ // --token <bearer>). ⭐ `--server <url>` names it; without the flag it is the DEFAULT
928
+ // backend — UNIWEB_SERVER, else ~/.uniweb/config.json, else https://uniweb.app —
920
929
  // and never the project's backend *[Diego, 2026-09-21: "the default backend for login, if
921
930
  // not specified, is uniweb.app"]*. ⭐ Except a WORKSPACE SWITCH — `--org` / `--personal`
922
931
  // and no way of signing in — which acts on the backend you are logged in to *[Diego,
923
932
  // 2026-10-06]* (`resolveLoginOrigin`). The backend logged in to becomes CURRENT, and
924
- // every backend command goes there (only UNIWEB_REGISTER_URL outranks it) — which is why
933
+ // every backend command goes there (only UNIWEB_SERVER outranks it) — which is why
925
934
  // this is the one place a backend is chosen.
926
935
  //
927
936
  // ⛔ Until 2026-09-21 a bare login went to the backend of the project in the cwd, and
@@ -932,12 +941,19 @@ async function main() {
932
941
  const { readFlagValue } = await import('./utils/args.js')
933
942
  const { runRegistryLogin } = await import('./utils/registry-auth.js')
934
943
  const { resolveLoginOrigin } = await import('./utils/config.js')
944
+ // `--backend` was this flag's name until 2026-10-07 — renamed so it is not read as a
945
+ // site's `backend` service. Refused with the new name, never treated as unknown and dropped:
946
+ // dropped, the login would go to the default backend.
947
+ if (loginArgs.some((a) => a === '--backend' || a.startsWith('--backend='))) {
948
+ console.error('\x1b[31m✗\x1b[0m `--backend` is now `--server`: uniweb login --server <url>')
949
+ process.exit(2)
950
+ }
935
951
  let apiBase
936
952
  try {
937
- apiBase = resolveLoginOrigin(readFlagValue(loginArgs, '--backend'), loginArgs)
953
+ apiBase = resolveLoginOrigin(readFlagValue(loginArgs, '--server'), loginArgs)
938
954
  } catch (err) {
939
955
  console.error(
940
- `\x1b[31m✗\x1b[0m ${err.message} — e.g. uniweb login --backend http://localhost:8080`
956
+ `\x1b[31m✗\x1b[0m ${err.message} — e.g. uniweb login --server http://localhost:8080`
941
957
  )
942
958
  process.exit(2)
943
959
  }
@@ -973,6 +989,19 @@ async function main() {
973
989
  return
974
990
  }
975
991
 
992
+ // Handle site command — the sites in a workspace: list them, unpublish or delete one.
993
+ // Runs anywhere: inside a project it can act on the site the project is synced to.
994
+ if (command === 'site') {
995
+ const { site } = await import('./commands/site.js')
996
+ const result = await site(args.slice(1))
997
+ // ⛔ EXIT ONLY ONCE STDOUT HAS DRAINED. To a pipe Node writes asynchronously, so an exit
998
+ // straight after a long list cut it at 8192 bytes — measured 2026-10-07: `site list --json`
999
+ // of 79 sites reached a reading process as half a JSON document, while the same command
1000
+ // into a file (synchronous) was whole. Pinned by test/site-list-pipe.test.js.
1001
+ await new Promise((resolve) => process.stdout.write('', resolve))
1002
+ process.exit(result?.exitCode ?? 0)
1003
+ }
1004
+
976
1005
  // Handle org command (new-backend orgs/units — publish-scope management)
977
1006
  if (command === 'org') {
978
1007
  const { org } = await import('./commands/org.js')
@@ -1796,7 +1825,7 @@ logged in to is where the backend commands go (push, pull, publish, status,
1796
1825
  register, clone) — so this is how you switch. Already logged in to it, login
1797
1826
  does nothing; add --password, --browser, --token-paste or --token to log in again.
1798
1827
 
1799
- Without --backend: https://uniweb.app (or \$UNIWEB_REGISTER_URL). No command talks
1828
+ Without --server: https://uniweb.app (or \$UNIWEB_SERVER). No command talks
1800
1829
  to a backend you are not logged in to — run one before logging in and it asks first.
1801
1830
 
1802
1831
  ${colors.bright}The workspace you work in.${colors.reset} A login works in ONE workspace — your
@@ -1807,7 +1836,7 @@ or name it. Already logged in, \`uniweb login --org @other\` (or \`--personal\`)
1807
1836
  the workspace on the backend you are logged in to, without logging in again.
1808
1837
 
1809
1838
  ${colors.bright}Options:${colors.reset}
1810
- --backend <url> The backend to log in to (without it: the default backend — or, for
1839
+ --server <url> The backend to log in to (without it: the default backend — or, for
1811
1840
  --org / --personal alone, the one you are logged in to)
1812
1841
  --org @org Work in @org (an organization you belong to)
1813
1842
  --personal Work in your personal workspace
@@ -1875,13 +1904,13 @@ so it is deliberately kept away from the refresh half.
1875
1904
  ${colors.cyan}${colors.bright}uniweb forget${colors.reset} ${colors.dim}— Remove what this project recorded about where it synced${colors.reset}
1876
1905
 
1877
1906
  ${colors.bright}Usage:${colors.reset}
1878
- uniweb forget --backend <url>
1907
+ uniweb forget --server <url>
1879
1908
  uniweb forget --all
1880
1909
 
1881
1910
  Local files only. Every site stays where it is on its backend.
1882
1911
 
1883
1912
  ${colors.bright}Options:${colors.reset}
1884
- --backend <url> Forget one backend: its entries in sync.json and in the local
1913
+ --server <url> Forget one backend: its entries in sync.json and in the local
1885
1914
  cache, and its deploy records in deploy.yml. Its targets stay,
1886
1915
  so the next publish there creates a new site.
1887
1916
  --all For a COPY of a project that should become a new one. Deletes
@@ -2062,6 +2091,7 @@ ${colors.bright}Commands:${colors.reset}
2062
2091
  sync Catch up, then push (refresh + push)
2063
2092
  status Show a site's sync state (unpushed content, foundation)
2064
2093
  forget Forget a backend, or --all to make a copied project a new one
2094
+ site <cmd> The sites in a workspace: list, unpublish, delete
2065
2095
  inspect <path> Inspect parsed content shape of a markdown file or folder
2066
2096
  docs Generate component documentation
2067
2097
  families List the standard section families (for meta.js family:)
@@ -2093,8 +2123,8 @@ ${colors.bright}Global Options:${colors.reset}
2093
2123
 
2094
2124
  Backend commands (push, pull, publish, status, register, clone) go to the
2095
2125
  backend you are logged in to, with its session. Switch with
2096
- \`uniweb login --backend <url>\` (add \`--token <bearer>\` to sign in with a token);
2097
- a script can aim and authenticate one process with UNIWEB_REGISTER_URL and
2126
+ \`uniweb login --server <url>\` (add \`--token <bearer>\` to sign in with a token);
2127
+ a script can aim and authenticate one process with UNIWEB_SERVER and
2098
2128
  UNIWEB_TOKEN instead.
2099
2129
 
2100
2130
  ${colors.bright}Push Options:${colors.reset}
@@ -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
- }