uniweb 0.48.0 → 0.48.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.48.0",
3
+ "version": "0.48.1",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,14 +42,14 @@
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
44
  "@uniweb/core": "^0.24.2",
45
- "@uniweb/semantic-parser": "^1.4.0",
46
45
  "@uniweb/kit": "^0.18.0",
46
+ "@uniweb/semantic-parser": "^1.4.0",
47
47
  "@uniweb/runtime": "^0.19.3"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/build": "^0.44.0",
51
- "@uniweb/semantic-parser": "^1.4.0",
52
- "@uniweb/content-reader": "^1.2.4"
50
+ "@uniweb/build": "^0.44.1",
51
+ "@uniweb/content-reader": "^1.2.4",
52
+ "@uniweb/semantic-parser": "^1.4.0"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -1249,8 +1249,16 @@ async function addCi(rootDir, opts, pm = 'pnpm') {
1249
1249
  let getAdapter, listAdapters
1250
1250
  try {
1251
1251
  ;({ getAdapter, listAdapters } = await import('@uniweb/build/hosts'))
1252
- } catch {
1253
- error('Failed to load host adapter registry from @uniweb/build/hosts.')
1252
+ } catch (err) {
1253
+ // Keep the reason. A bare `catch {}` here printed one line for three
1254
+ // different failures — the package is absent, the `./hosts` subpath is
1255
+ // not exported by the installed version, or the module threw while
1256
+ // evaluating — and they have different remedies.
1257
+ error(`Failed to load the host adapter registry from @uniweb/build/hosts.`)
1258
+ error(` ${err.message}`)
1259
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
1260
+ error(` Install the workspace's dependencies, or upgrade @uniweb/build.`)
1261
+ }
1254
1262
  process.exit(1)
1255
1263
  }
1256
1264
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * template — RESERVED (not available on the new backend yet).
2
+ * template — `list` is live; `register` is RESERVED (not on the new backend yet).
3
3
  *
4
4
  * `uniweb template publish` submitted a site as a cloud template to the legacy
5
5
  * registry (the PHP backend + Cloudflare Worker) the CLI no longer talks to. A
@@ -9,9 +9,35 @@
9
9
  *
10
10
  * (Unrelated: scaffolding FROM a template — `uniweb create --template <name>` —
11
11
  * is a separate path and is unaffected.)
12
+ *
13
+ * ⭐ `list` exists so a SCRIPT can enumerate the official templates. The
14
+ * interactive picker in `create` has always shown this list; `--json` is the
15
+ * same data, second rendering, for tooling that scaffolds several templates in
16
+ * a row and needs their names first.
17
+ *
18
+ * ⚠️ IT REPORTS A BUNDLED SNAPSHOT, NOT A LIVE FETCH — the roster is baked into
19
+ * the CLI at publish time (`src/framework-index.json`, rewritten from
20
+ * `templates/manifest.json` by the pre-publish hook). So it answers "which
21
+ * official templates does THIS CLI know", which is the right question when the
22
+ * next step is resolving one of those names with the same CLI. `cliVersion` is
23
+ * in the JSON for exactly that reason. A newer template than your CLI will not
24
+ * appear — and an unknown name is not fatal either way, since `create` falls
25
+ * through to npm `@uniweb/template-<name>`, so this is the OFFICIAL roster
26
+ * rather than the set of resolvable names.
12
27
  */
13
28
 
14
- export async function template() {
29
+ import { OFFICIAL_TEMPLATE_MAP } from '../templates/resolver.js'
30
+ import { getCliVersion } from '../versions.js'
31
+
32
+ const colors = {
33
+ reset: '\x1b[0m',
34
+ bright: '\x1b[1m',
35
+ dim: '\x1b[2m',
36
+ cyan: '\x1b[36m',
37
+ red: '\x1b[31m'
38
+ }
39
+
40
+ function reserved() {
15
41
  console.error(
16
42
  "\x1b[31m✗\x1b[0m `uniweb template register` isn't available on the new backend yet."
17
43
  )
@@ -24,7 +50,69 @@ export async function template() {
24
50
  console.error(
25
51
  ' (Scaffolding FROM a template still works: `uniweb create --template <name>`.)'
26
52
  )
53
+ console.error('')
54
+ console.error(' To list the official templates: `uniweb template list`.')
27
55
  process.exit(1)
28
56
  }
29
57
 
58
+ function listTemplates(args) {
59
+ const entries = Object.entries(OFFICIAL_TEMPLATE_MAP).map(([id, info]) => ({
60
+ id,
61
+ name: info?.name || id,
62
+ description: info?.description || '',
63
+ tags: info?.tags || []
64
+ }))
65
+
66
+ if (args.includes('--json')) {
67
+ // ⛔ stdout carries JSON and nothing else, so it pipes. Every human-facing
68
+ // line in this command goes to stderr for the same reason.
69
+ process.stdout.write(
70
+ JSON.stringify(
71
+ { cliVersion: getCliVersion(), count: entries.length, templates: entries },
72
+ null,
73
+ 2
74
+ ) + '\n'
75
+ )
76
+ return
77
+ }
78
+
79
+ if (entries.length === 0) {
80
+ // Not a crash: a CLI whose snapshot failed to load falls back to an empty
81
+ // map, and `create` then routes every name to npm. Say which happened.
82
+ console.error(
83
+ `${colors.red}✗${colors.reset} No official templates found in this CLI's bundled index.`
84
+ )
85
+ console.error(
86
+ ` ${colors.dim}A template name will still resolve through npm as @uniweb/template-<name>.${colors.reset}`
87
+ )
88
+ process.exit(1)
89
+ }
90
+
91
+ const width = Math.max(...entries.map((e) => e.id.length))
92
+ console.log('')
93
+ console.log(
94
+ `${colors.bright}Official templates${colors.reset} ${colors.dim}(uniweb ${getCliVersion()})${colors.reset}`
95
+ )
96
+ console.log('')
97
+ for (const e of entries) {
98
+ console.log(
99
+ ` ${colors.cyan}${e.id.padEnd(width)}${colors.reset} ${e.description || e.name}`
100
+ )
101
+ }
102
+ console.log('')
103
+ console.log(
104
+ ` ${colors.dim}uniweb create <project> --template <name>${colors.reset}`
105
+ )
106
+ console.log(
107
+ ` ${colors.dim}--json for scripts. This is the roster THIS CLI ships with, not a live fetch.${colors.reset}`
108
+ )
109
+ console.log('')
110
+ }
111
+
112
+ export async function template(args = []) {
113
+ const sub = args.find((a) => !a.startsWith('-'))
114
+ if (sub === 'list') return listTemplates(args)
115
+ return reserved()
116
+ }
117
+
30
118
  export default template
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-09-09T20:26:27.202Z",
3
+ "generatedAt": "2026-09-09T22:45:28.403Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "0.2.13",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "@uniweb/build": {
13
- "version": "0.44.0",
13
+ "version": "0.44.1",
14
14
  "path": "framework/build",
15
15
  "deps": [
16
16
  "@uniweb/content-reader",
@@ -120,7 +120,7 @@
120
120
  "deps": []
121
121
  },
122
122
  "@uniweb/unipress": {
123
- "version": "0.9.11",
123
+ "version": "0.9.12",
124
124
  "path": "framework/unipress",
125
125
  "deps": [
126
126
  "@uniweb/build",
package/src/index.js CHANGED
@@ -55,6 +55,7 @@ import {
55
55
  } from './utils/scaffold.js'
56
56
  import {
57
57
  detectPackageManager,
58
+ detectWorkspacePm,
58
59
  filterCmd,
59
60
  installCmd,
60
61
  runCmd,
@@ -224,32 +225,98 @@ function delegateToLocal(localCliPath) {
224
225
  }
225
226
 
226
227
  /**
227
- * Import a command module that may depend on @uniweb/build.
228
- * Provides a helpful error when the dependency can't be resolved
229
- * (e.g., running a project-bound command from a global install
230
- * outside a project directory).
228
+ * Import a command module that may depend on an optional @uniweb/* peer
229
+ * (`@uniweb/build` above all). Sixteen commands load through here, so this
230
+ * is the error most users meet when something is not installed.
231
+ *
232
+ * ⛔ IT MUST NOT NAME A CAUSE IT HAS NOT CHECKED. This said "This command
233
+ * must be run from inside a Uniweb project" for every `ERR_MODULE_NOT_FOUND`
234
+ * on a `@uniweb/*` specifier — a guess, and wrong in the two cases that do
235
+ * not involve your location at all: a workspace whose dependencies are simply
236
+ * not installed, and a CLI installed on its own (the optional peers are not
237
+ * installed with it, so `npm install uniweb --prefix X` yields exactly this).
238
+ * Project resolution is cwd-based (`findWorkspaceRoot`) and always was, so
239
+ * the check the message needed was one call away and already imported here.
240
+ *
241
+ * ⚠️ A wrong cause is worse than a vague one: it does not merely fail to
242
+ * explain, it DIRECTS the investigation. Reported 2026-09-09 by a consumer
243
+ * who lost an hour testing the cause this text named, having already done
244
+ * the `cd` and the install it advised.
231
245
  */
232
246
  async function importProjectCommand(modulePath) {
233
247
  try {
234
248
  return await import(modulePath)
235
249
  } catch (err) {
236
250
  if (
237
- err.code === 'ERR_MODULE_NOT_FOUND' &&
238
- err.message?.includes('@uniweb/')
251
+ err.code !== 'ERR_MODULE_NOT_FOUND' ||
252
+ !err.message?.includes('@uniweb/')
239
253
  ) {
240
- error('This command must be run from inside a Uniweb project.')
254
+ throw err
255
+ }
256
+
257
+ // Name the package that actually failed to resolve, rather than making
258
+ // the reader guess which of the optional peers this command needed.
259
+ const missing =
260
+ err.message.match(/'(@uniweb\/[^']+)'/)?.[1] ?? 'a @uniweb/* package'
261
+ const root = findWorkspaceRoot(process.cwd())
262
+
263
+ // ⛔ THREE STATES, and the second and third look identical from here — the
264
+ // import failed either way. Discriminate on what is ON DISK, because
265
+ // "declared but not installed" and "installed but not reachable from where
266
+ // THIS CLI lives" have opposite remedies, and telling a fully-installed
267
+ // workspace to run its installer is the same wrong-cause defect again.
268
+ const workspaceModules = root ? join(root, 'node_modules') : null
269
+ const workspaceInstalled = !!workspaceModules && existsSync(workspaceModules)
270
+ const localCli = root ? join(workspaceModules, '.bin', 'uniweb') : null
271
+
272
+ error(`Cannot load ${missing}, which this command needs.`)
273
+ log('')
274
+
275
+ if (!root) {
276
+ log(
277
+ `No Uniweb workspace found above ${colors.dim}${process.cwd()}${colors.reset}.`
278
+ )
279
+ log(` ${colors.cyan}cd${colors.reset} into a project, or create one:`)
280
+ log(` ${colors.cyan}uniweb create my-project${colors.reset}`)
241
281
  log('')
242
282
  log(
243
- `Make sure you're in a project directory with dependencies installed:`
283
+ `${colors.dim}Driving projects from a standalone CLI? ${missing} is an optional${colors.reset}`
284
+ )
285
+ log(
286
+ `${colors.dim}peer, so a bare \`npm i uniweb\` does not bring it:${colors.reset}`
244
287
  )
245
- log(` ${colors.cyan}cd your-project${colors.reset}`)
246
- log(` ${colors.cyan}npm install${colors.reset}`)
288
+ log(` ${colors.cyan}npm install uniweb ${missing}${colors.reset}`)
289
+ } else if (!workspaceInstalled) {
290
+ // Declared, not on disk. Prefer the workspace's own package manager,
291
+ // read off its lockfile — but a workspace that has NEVER been installed
292
+ // has no lockfile to read, so fall back the same way `create` does
293
+ // rather than to whichever PM happens to be running this process.
294
+ const pm =
295
+ detectWorkspacePm(root) ||
296
+ (isPnpmAvailable() ? 'pnpm' : detectPackageManager())
297
+ log(`Uniweb workspace: ${colors.dim}${root}${colors.reset}`)
298
+ log(`Its dependencies are not installed:`)
299
+ log(` ${colors.cyan}${installCmd(pm)}${colors.reset}`)
300
+ } else {
301
+ // Installed, and still unreachable: Node resolves a command's imports
302
+ // from the CLI's own location, so a CLI installed elsewhere cannot see
303
+ // this workspace's copy. Both remedies are real; the local bin is the
304
+ // one that guarantees the project's own pinned toolchain does the work.
305
+ log(
306
+ `Uniweb workspace: ${colors.dim}${root}${colors.reset} ${colors.dim}(dependencies are installed)${colors.reset}`
307
+ )
308
+ log(
309
+ `This CLI resolves its imports from its own install location, not from`
310
+ )
311
+ log(`the workspace, so it cannot see the workspace's ${missing}.`)
247
312
  log('')
248
- log(`Or create a new project:`)
249
- log(` ${colors.cyan}uniweb create my-project${colors.reset}`)
250
- process.exit(1)
313
+ log(`Use the project's own CLI:`)
314
+ log(` ${colors.cyan}${localCli}${colors.reset}`)
315
+ log(`or install the peer next to this one:`)
316
+ log(` ${colors.cyan}npm install uniweb ${missing}${colors.reset}`)
251
317
  }
252
- throw err
318
+
319
+ process.exit(1)
253
320
  }
254
321
  }
255
322
 
@@ -1640,11 +1707,26 @@ The site-handoff flow was retired with the legacy backend.
1640
1707
  Manage client sites from the Uniweb app for now.
1641
1708
  `,
1642
1709
  template: `
1643
- ${colors.cyan}${colors.bright}uniweb template${colors.reset} ${colors.dim}— (reserved; not available on the new backend yet)${colors.reset}
1710
+ ${colors.cyan}${colors.bright}uniweb template${colors.reset} ${colors.dim}— Official templates${colors.reset}
1644
1711
 
1645
- Submitting a site as a cloud template was retired with the legacy backend.
1646
- When rebuilt, a template is REGISTERED (like a foundation) — \`publish\` is for
1647
- sites only. Scaffolding FROM a template still works: \`uniweb create --template <name>\`.
1712
+ ${colors.bright}Usage:${colors.reset}
1713
+ uniweb template list List the official templates
1714
+ uniweb template list --json Same, machine-readable (for scripts)
1715
+
1716
+ ${colors.bright}What \`list\` reports:${colors.reset}
1717
+ The roster ${colors.bright}this CLI ships with${colors.reset} — a snapshot baked in at publish time,
1718
+ not a live fetch. That is the right question when the next step is resolving
1719
+ one of those names with the same CLI, which is why \`--json\` stamps
1720
+ \`cliVersion\`. A template newer than your CLI will not be listed.
1721
+
1722
+ It is the ${colors.bright}official${colors.reset} roster, not the set of resolvable names: an unknown
1723
+ name is not fatal, since \`create\` falls through to npm \`@uniweb/template-<name>\`.
1724
+
1725
+ ${colors.bright}Reserved:${colors.reset}
1726
+ \`uniweb template register\` is not available on the new backend yet. Submitting
1727
+ a site as a cloud template was retired with the legacy backend; when rebuilt, a
1728
+ template is REGISTERED (like a foundation) — \`publish\` is for sites only.
1729
+ Scaffolding FROM a template is unaffected: \`uniweb create --template <name>\`.
1648
1730
  `,
1649
1731
  docs: `
1650
1732
  ${colors.cyan}${colors.bright}uniweb docs${colors.reset} ${colors.dim}— Generate component documentation${colors.reset}