uniweb 0.12.52 → 0.12.54

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.12.52",
3
+ "version": "0.12.54",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,14 +41,14 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/core": "0.7.21",
45
- "@uniweb/runtime": "0.8.27",
46
- "@uniweb/kit": "0.9.26"
44
+ "@uniweb/core": "0.7.22",
45
+ "@uniweb/kit": "0.9.28",
46
+ "@uniweb/runtime": "0.8.28"
47
47
  },
48
48
  "peerDependencies": {
49
- "@uniweb/build": "0.14.33",
50
- "@uniweb/content-reader": "1.1.12",
51
- "@uniweb/semantic-parser": "1.1.17"
49
+ "@uniweb/build": "0.14.35",
50
+ "@uniweb/content-reader": "1.1.13",
51
+ "@uniweb/semantic-parser": "1.1.18"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@uniweb/build": {
@@ -27,7 +27,7 @@ import {
27
27
  import { discoverFoundations, discoverSites } from '../utils/discover.js'
28
28
  import { validatePackageName, getExistingPackageNames, resolveUniqueName } from '../utils/names.js'
29
29
  import { findWorkspaceRoot } from '../utils/workspace.js'
30
- import { detectPackageManager, detectWorkspacePm, filterCmd, installCmd } from '../utils/pm.js'
30
+ import { detectPackageManager, detectWorkspacePm, detectInstalledPnpmVersion, filterCmd, installCmd } from '../utils/pm.js'
31
31
  import { isNonInteractive, getCliPrefix, stripNonInteractiveFlag, formatOptions } from '../utils/interactive.js'
32
32
  import { resolveTemplate } from '../templates/index.js'
33
33
  import { validateTemplate } from '../templates/validator.js'
@@ -1156,9 +1156,13 @@ async function addCi(rootDir, opts, pm = 'pnpm') {
1156
1156
  // CI must run the project's own toolchain. The pnpm major comes from
1157
1157
  // its `packageManager` field when declared; the node major is the
1158
1158
  // project's floor, raised if that pnpm needs more.
1159
- const pnpmVersion = resolveCiPnpmVersion(rootPkg)
1159
+ const installedPnpm = detectInstalledPnpmVersion(rootDir)
1160
+ const pnpmVersion = resolveCiPnpmVersion(rootPkg, installedPnpm)
1160
1161
  const nodeVersion = resolveCiNodeVersion(rootPkg.engines?.node, pm, pnpmVersion)
1161
- reportCiToolchain({ pm, pnpmVersion, nodeVersion, rootPkg })
1162
+ reportCiToolchain({
1163
+ pm, pnpmVersion, nodeVersion,
1164
+ source: rootPkg?.packageManager ? 'declared' : installedPnpm ? 'installed' : 'fallback',
1165
+ })
1162
1166
 
1163
1167
  const siteDir = join(rootDir, site.path)
1164
1168
  if (!resolvedDomain) {
@@ -1284,19 +1288,28 @@ function stripScope(name) {
1284
1288
  }
1285
1289
 
1286
1290
  /**
1287
- * Print the toolchain the generated workflow will install.
1291
+ * Print the toolchain the generated workflow will install, and where each
1292
+ * part came from.
1288
1293
  *
1289
- * These versions are inferred whenever the project doesn't state them, and a
1290
- * silent inference is exactly what let a wrong pnpm/Node pairing ship
1291
- * unnoticed. Showing what resolved and how to override it — follows the
1292
- * same "resolve, then show what resolved" rule the publish scope picker uses.
1294
+ * These values are resolved rather than stated whenever the project is
1295
+ * silent, and a silent resolution is what let a broken pnpm/Node pairing
1296
+ * ship unnoticed. Naming the source matters too: "we read this from your
1297
+ * install" invites a different reaction than "we guessed", and only the
1298
+ * latter needs an override hint.
1293
1299
  */
1294
- function reportCiToolchain({ pm, pnpmVersion, nodeVersion, rootPkg }) {
1295
- const declared = typeof rootPkg?.packageManager === 'string'
1296
- const tool = pm === 'pnpm' ? `pnpm ${pnpmVersion}` : pm === 'yarn' ? 'yarn' : 'npm'
1297
- info(`CI will use ${tool} + Node ${nodeVersion}${declared ? '' : ' (inferred)'}`)
1298
- if (!declared && pm === 'pnpm') {
1299
- info(`Pin with "packageManager": "pnpm@<version>" in package.json to use a different major.`)
1300
+ function reportCiToolchain({ pm, pnpmVersion, nodeVersion, source }) {
1301
+ if (pm !== 'pnpm') {
1302
+ info(`CI will use ${pm} + Node ${nodeVersion}`)
1303
+ return
1304
+ }
1305
+ const origin = {
1306
+ declared: '',
1307
+ installed: ' (matching your install)',
1308
+ fallback: ' (no pnpm detected — using the default)',
1309
+ }[source] ?? ''
1310
+ info(`CI will use pnpm ${pnpmVersion} + Node ${nodeVersion}${origin}`)
1311
+ if (source === 'fallback') {
1312
+ info(`Pin with "packageManager": "pnpm@<version>" in package.json to choose a major.`)
1300
1313
  }
1301
1314
  }
1302
1315
 
@@ -1358,9 +1371,13 @@ async function addFoundationCi(rootDir, opts, adapter, pm) {
1358
1371
  // CI must run the project's own toolchain. The pnpm major comes from
1359
1372
  // its `packageManager` field when declared; the node major is the
1360
1373
  // project's floor, raised if that pnpm needs more.
1361
- const pnpmVersion = resolveCiPnpmVersion(rootPkg)
1374
+ const installedPnpm = detectInstalledPnpmVersion(rootDir)
1375
+ const pnpmVersion = resolveCiPnpmVersion(rootPkg, installedPnpm)
1362
1376
  const nodeVersion = resolveCiNodeVersion(rootPkg.engines?.node, pm, pnpmVersion)
1363
- reportCiToolchain({ pm, pnpmVersion, nodeVersion, rootPkg })
1377
+ reportCiToolchain({
1378
+ pm, pnpmVersion, nodeVersion,
1379
+ source: rootPkg?.packageManager ? 'declared' : installedPnpm ? 'installed' : 'fallback',
1380
+ })
1364
1381
 
1365
1382
  let result
1366
1383
  try {
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-22T08:09:32.588Z",
3
+ "generatedAt": "2026-07-22T09:39:33.879Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.14.33",
6
+ "version": "0.14.35",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",
@@ -15,7 +15,7 @@
15
15
  ]
16
16
  },
17
17
  "@uniweb/content-reader": {
18
- "version": "1.1.12",
18
+ "version": "1.1.13",
19
19
  "path": "framework/content-reader",
20
20
  "deps": []
21
21
  },
@@ -25,7 +25,7 @@
25
25
  "deps": []
26
26
  },
27
27
  "@uniweb/core": {
28
- "version": "0.7.21",
28
+ "version": "0.7.22",
29
29
  "path": "framework/core",
30
30
  "deps": [
31
31
  "@uniweb/semantic-parser",
@@ -43,7 +43,7 @@
43
43
  "deps": []
44
44
  },
45
45
  "@uniweb/kit": {
46
- "version": "0.9.26",
46
+ "version": "0.9.28",
47
47
  "path": "framework/kit",
48
48
  "deps": [
49
49
  "@uniweb/core",
@@ -61,7 +61,7 @@
61
61
  "deps": []
62
62
  },
63
63
  "@uniweb/runtime": {
64
- "version": "0.8.27",
64
+ "version": "0.8.28",
65
65
  "path": "framework/runtime",
66
66
  "deps": [
67
67
  "@uniweb/core",
@@ -84,7 +84,7 @@
84
84
  "deps": []
85
85
  },
86
86
  "@uniweb/semantic-parser": {
87
- "version": "1.1.17",
87
+ "version": "1.1.18",
88
88
  "path": "framework/semantic-parser",
89
89
  "deps": []
90
90
  },
@@ -99,7 +99,7 @@
99
99
  "deps": []
100
100
  },
101
101
  "@uniweb/unipress": {
102
- "version": "0.4.41",
102
+ "version": "0.4.42",
103
103
  "path": "framework/unipress",
104
104
  "deps": [
105
105
  "@uniweb/build",
package/src/utils/pm.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * and generate PM-appropriate commands for output messages.
6
6
  */
7
7
 
8
- import { existsSync } from 'node:fs'
8
+ import { existsSync, readFileSync } from 'node:fs'
9
9
  import { join } from 'node:path'
10
10
  import { execSync } from 'node:child_process'
11
11
 
@@ -41,6 +41,48 @@ export function detectWorkspacePm(workspaceRoot) {
41
41
  return null
42
42
  }
43
43
 
44
+ /**
45
+ * The pnpm major that actually installed this workspace.
46
+ *
47
+ * pnpm records the version it ran as in `node_modules/.modules.yaml`:
48
+ *
49
+ * "packageManager": "pnpm@10.30.0"
50
+ *
51
+ * This is a *record of what happened*, not a guess about intent — which
52
+ * matters, because intent cannot be inferred. The documented way to
53
+ * scaffold is `npm create uniweb`, and plenty of people follow it with
54
+ * `pnpm install`; the invoking package manager therefore says nothing
55
+ * about the one the project uses. The lockfile says WHICH manager
56
+ * (`detectWorkspacePm`); this says which pnpm MAJOR, which the lockfile
57
+ * cannot — pnpm 10 and 11 both write `lockfileVersion: 9.0`.
58
+ *
59
+ * Despite the extension the file is JSON in current pnpm; `js-yaml`
60
+ * parses both, since YAML is a superset. Older pnpm wrote real YAML.
61
+ *
62
+ * @param {string} workspaceRoot
63
+ * @returns {string|null} pnpm major (e.g. '10'), or null when the file is
64
+ * absent or unreadable — callers fall back to their own default.
65
+ */
66
+ export function detectInstalledPnpmVersion(workspaceRoot) {
67
+ if (!workspaceRoot) return null
68
+ const path = join(workspaceRoot, 'node_modules', '.modules.yaml')
69
+ if (!existsSync(path)) return null
70
+ try {
71
+ // Cheap targeted read: the file is large (hoisted-dependency maps run
72
+ // to hundreds of KB) and we want exactly one field, so match it
73
+ // directly rather than parsing the whole document.
74
+ const text = readFileSync(path, 'utf8')
75
+ // Quotes optional on both key and value: current pnpm writes JSON
76
+ // (`"packageManager": "pnpm@10.30.0"`), but the file is named .yaml and
77
+ // YAML permits the bare form, so accept either rather than depending on
78
+ // which serializer the installing pnpm happened to use.
79
+ const match = text.match(/["']?packageManager["']?\s*:\s*["']?pnpm@(\d+)/)
80
+ return match ? match[1] : null
81
+ } catch {
82
+ return null
83
+ }
84
+ }
85
+
44
86
  /**
45
87
  * Detect which package manager owns a *globally installed* `uniweb` CLI
46
88
  * binary, by inspecting its install path (`process.argv[1]`). pnpm and
package/src/versions.js CHANGED
@@ -100,20 +100,37 @@ export const PNPM_MIN_NODE = { '10': 18, '11': 22 }
100
100
  /**
101
101
  * Resolve the pnpm major a generated CI workflow should install.
102
102
  *
103
- * `packageManager` (the corepack field) is authoritative when present — it is
104
- * the project stating which pnpm it is developed and locked against, so CI
105
- * should honour it rather than impose a different major.
103
+ * Observed, not assumed, in this order:
106
104
  *
107
- * `pnpm-lock.yaml`'s `lockfileVersion` is NOT usable as a signal: pnpm 10 and
108
- * 11 both write `9.0`, so it cannot distinguish them. Checked, not assumed.
105
+ * 1. `packageManager` in package.json the project stating explicitly
106
+ * which pnpm it is developed and locked against. Honour it.
107
+ * 2. What actually installed the project, from
108
+ * `node_modules/.modules.yaml` (`detectInstalledPnpmVersion`). A record
109
+ * of fact: the developer running `add ci` is the developer who ran
110
+ * `pnpm install`, and `add ci` refuses to run before install, so this
111
+ * is normally present.
112
+ * 3. `PNPM_VERSION` — a last resort that should rarely apply.
113
+ *
114
+ * There is deliberately no inference from *how the CLI was invoked*. The
115
+ * documented way to scaffold is `npm create uniweb`, commonly followed by
116
+ * `pnpm install`, so the invoking manager says nothing about the project's.
117
+ * Writing a `packageManager` field at scaffold time was considered and
118
+ * rejected for the same reason — it would guess intent and, being enforced
119
+ * by corepack, would be a hard pin rather than a hint.
120
+ *
121
+ * `pnpm-lock.yaml`'s `lockfileVersion` is NOT usable here: pnpm 10 and 11
122
+ * both write `9.0`, so it cannot distinguish them. Checked, not assumed.
109
123
  *
110
124
  * @param {object|null} rootPkg — the workspace root's parsed package.json.
125
+ * @param {string|null} [installedMajor] — from `detectInstalledPnpmVersion`.
111
126
  * @returns {string} pnpm major, as a string for YAML interpolation.
112
127
  */
113
- export function resolveCiPnpmVersion(rootPkg) {
128
+ export function resolveCiPnpmVersion(rootPkg, installedMajor = null) {
114
129
  const declared = rootPkg?.packageManager
115
130
  const match = typeof declared === 'string' ? declared.match(/^pnpm@(\d+)/) : null
116
- return match ? match[1] : PNPM_VERSION
131
+ if (match) return match[1]
132
+ if (installedMajor) return String(installedMajor)
133
+ return PNPM_VERSION
117
134
  }
118
135
 
119
136
  /**