uniweb 0.12.47 → 0.12.48
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 +3 -3
- package/src/commands/add.js +66 -28
- package/src/framework-index.json +3 -3
- package/src/versions.js +78 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.48",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"@uniweb/runtime": "0.8.26"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
+
"@uniweb/build": "0.14.30",
|
|
49
50
|
"@uniweb/content-reader": "1.1.12",
|
|
50
|
-
"@uniweb/semantic-parser": "1.1.17"
|
|
51
|
-
"@uniweb/build": "0.14.29"
|
|
51
|
+
"@uniweb/semantic-parser": "1.1.17"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@uniweb/build": {
|
package/src/commands/add.js
CHANGED
|
@@ -27,11 +27,11 @@ 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, filterCmd, installCmd } from '../utils/pm.js'
|
|
30
|
+
import { detectPackageManager, detectWorkspacePm, 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'
|
|
34
|
-
import { getVersionsForTemplates,
|
|
34
|
+
import { getVersionsForTemplates, resolveCiPnpmVersion, resolveCiNodeVersion } from '../versions.js'
|
|
35
35
|
|
|
36
36
|
// Colors for terminal output
|
|
37
37
|
const colors = {
|
|
@@ -76,10 +76,41 @@ function parseArgs(args) {
|
|
|
76
76
|
// doesn't accidentally swallow the next positional.
|
|
77
77
|
const BOOLEAN_FLAGS = new Set(['--force', '--no-previews'])
|
|
78
78
|
|
|
79
|
+
// Value flags, mapped to their result key. Both spellings are accepted:
|
|
80
|
+
// `--host github-pages` and `--host=github-pages`.
|
|
81
|
+
//
|
|
82
|
+
// The `=` form used to be dropped silently — every branch matched on an
|
|
83
|
+
// exact `args[i] === '--flag'`, so `--host=github-pages` fell through
|
|
84
|
+
// and left `host` null. It went unnoticed because `add ci` defaulted to
|
|
85
|
+
// github-pages when no host was given, making the one command our docs
|
|
86
|
+
// print (`uniweb add ci --host=github-pages`) appear to work while
|
|
87
|
+
// actually ignoring the flag. Requiring a real host surfaced it.
|
|
88
|
+
const VALUE_FLAGS = {
|
|
89
|
+
'--path': 'path',
|
|
90
|
+
'--project': 'project',
|
|
91
|
+
'--foundation': 'foundation',
|
|
92
|
+
'--site': 'site',
|
|
93
|
+
'--from': 'from',
|
|
94
|
+
'--host': 'host',
|
|
95
|
+
'--domain': 'domain',
|
|
96
|
+
'--target': 'target',
|
|
97
|
+
'--project-name': 'projectName',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Split `--flag=value` into [flag, value]; `--flag` into [flag, null]. */
|
|
101
|
+
const splitFlag = (arg) => {
|
|
102
|
+
const eq = arg.indexOf('=')
|
|
103
|
+
return eq === -1 ? [arg, null] : [arg.slice(0, eq), arg.slice(eq + 1)]
|
|
104
|
+
}
|
|
105
|
+
|
|
79
106
|
// Find positional name (first arg after subcommand that's not a flag).
|
|
80
107
|
for (let i = 1; i < args.length; i++) {
|
|
81
108
|
if (args[i].startsWith('--')) {
|
|
82
|
-
|
|
109
|
+
const [flag, inlineValue] = splitFlag(args[i])
|
|
110
|
+
// Only skip the next arg when the value is genuinely separate.
|
|
111
|
+
// Previously an `=`-form flag also consumed the following arg,
|
|
112
|
+
// which could swallow the positional name.
|
|
113
|
+
if (!BOOLEAN_FLAGS.has(flag) && inlineValue === null) i++
|
|
83
114
|
continue
|
|
84
115
|
}
|
|
85
116
|
if (!result.name) {
|
|
@@ -89,27 +120,17 @@ function parseArgs(args) {
|
|
|
89
120
|
|
|
90
121
|
// Parse flags
|
|
91
122
|
for (let i = 1; i < args.length; i++) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} else if (
|
|
101
|
-
result.from = args[++i]
|
|
102
|
-
} else if (args[i] === '--host' && args[i + 1]) {
|
|
103
|
-
result.host = args[++i]
|
|
104
|
-
} else if (args[i] === '--domain' && args[i + 1]) {
|
|
105
|
-
result.domain = args[++i]
|
|
106
|
-
} else if (args[i] === '--target' && args[i + 1]) {
|
|
107
|
-
result.target = args[++i]
|
|
108
|
-
} else if (args[i] === '--project-name' && args[i + 1]) {
|
|
109
|
-
result.projectName = args[++i]
|
|
110
|
-
} else if (args[i] === '--force') {
|
|
123
|
+
const [flag, inlineValue] = splitFlag(args[i])
|
|
124
|
+
const key = VALUE_FLAGS[flag]
|
|
125
|
+
if (key) {
|
|
126
|
+
if (inlineValue !== null) {
|
|
127
|
+
result[key] = inlineValue
|
|
128
|
+
} else if (args[i + 1] !== undefined && !args[i + 1].startsWith('--')) {
|
|
129
|
+
result[key] = args[++i]
|
|
130
|
+
}
|
|
131
|
+
} else if (flag === '--force') {
|
|
111
132
|
result.force = true
|
|
112
|
-
} else if (
|
|
133
|
+
} else if (flag === '--no-previews') {
|
|
113
134
|
result.previews = false
|
|
114
135
|
}
|
|
115
136
|
}
|
|
@@ -204,7 +225,16 @@ export async function add(rawArgs) {
|
|
|
204
225
|
await addSection(rootDir, parsed)
|
|
205
226
|
break
|
|
206
227
|
case 'ci':
|
|
207
|
-
|
|
228
|
+
// The scaffolded workflow must install the way THIS WORKSPACE
|
|
229
|
+
// installs, which is a property of the repo (its lockfile), not of
|
|
230
|
+
// how the CLI happened to be launched. `detectPackageManager()`
|
|
231
|
+
// reads npm_config_user_agent, so `npx uniweb add ci` in a pnpm
|
|
232
|
+
// workspace produced a workflow running `npm ci` — which fails on
|
|
233
|
+
// the first CI run, because there is no package-lock.json to
|
|
234
|
+
// install from. npx is a normal way to run the CLI, so this broke
|
|
235
|
+
// the common case. Fall back to the invocation only when the repo
|
|
236
|
+
// has no lockfile to read.
|
|
237
|
+
await addCi(rootDir, parsed, detectWorkspacePm(rootDir) || pm)
|
|
208
238
|
break
|
|
209
239
|
default:
|
|
210
240
|
error(`Unknown subcommand: ${parsed.subcommand}`)
|
|
@@ -1123,7 +1153,11 @@ async function addCi(rootDir, opts, pm = 'pnpm') {
|
|
|
1123
1153
|
const rootPkg = JSON.parse(
|
|
1124
1154
|
await readFile(join(rootDir, 'package.json'), 'utf-8').catch(() => '{}')
|
|
1125
1155
|
)
|
|
1126
|
-
|
|
1156
|
+
// CI must run the project's own toolchain. The pnpm major comes from
|
|
1157
|
+
// its `packageManager` field when declared; the node major is the
|
|
1158
|
+
// project's floor, raised if that pnpm needs more.
|
|
1159
|
+
const pnpmVersion = resolveCiPnpmVersion(rootPkg)
|
|
1160
|
+
const nodeVersion = resolveCiNodeVersion(rootPkg.engines?.node, pm, pnpmVersion)
|
|
1127
1161
|
|
|
1128
1162
|
const siteDir = join(rootDir, site.path)
|
|
1129
1163
|
if (!resolvedDomain) {
|
|
@@ -1145,7 +1179,7 @@ async function addCi(rootDir, opts, pm = 'pnpm') {
|
|
|
1145
1179
|
target: 'site',
|
|
1146
1180
|
packageManager: pm,
|
|
1147
1181
|
nodeVersion,
|
|
1148
|
-
pnpmVersion
|
|
1182
|
+
pnpmVersion,
|
|
1149
1183
|
domain: resolvedDomain,
|
|
1150
1184
|
previews: opts.previews !== false,
|
|
1151
1185
|
projectName: resolveHostProjectName(rootDir, rootPkg, site, sites.length, opts),
|
|
@@ -1303,7 +1337,11 @@ async function addFoundationCi(rootDir, opts, adapter, pm) {
|
|
|
1303
1337
|
const rootPkg = JSON.parse(
|
|
1304
1338
|
await readFile(join(rootDir, 'package.json'), 'utf-8').catch(() => '{}')
|
|
1305
1339
|
)
|
|
1306
|
-
|
|
1340
|
+
// CI must run the project's own toolchain. The pnpm major comes from
|
|
1341
|
+
// its `packageManager` field when declared; the node major is the
|
|
1342
|
+
// project's floor, raised if that pnpm needs more.
|
|
1343
|
+
const pnpmVersion = resolveCiPnpmVersion(rootPkg)
|
|
1344
|
+
const nodeVersion = resolveCiNodeVersion(rootPkg.engines?.node, pm, pnpmVersion)
|
|
1307
1345
|
|
|
1308
1346
|
let result
|
|
1309
1347
|
try {
|
|
@@ -1313,7 +1351,7 @@ async function addFoundationCi(rootDir, opts, adapter, pm) {
|
|
|
1313
1351
|
target: 'foundation',
|
|
1314
1352
|
packageManager: pm,
|
|
1315
1353
|
nodeVersion,
|
|
1316
|
-
pnpmVersion
|
|
1354
|
+
pnpmVersion,
|
|
1317
1355
|
})
|
|
1318
1356
|
} catch (err) {
|
|
1319
1357
|
error(err.message)
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-21T20:48:30.091Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.14.
|
|
6
|
+
"version": "0.14.30",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"deps": []
|
|
100
100
|
},
|
|
101
101
|
"@uniweb/unipress": {
|
|
102
|
-
"version": "0.4.
|
|
102
|
+
"version": "0.4.37",
|
|
103
103
|
"path": "framework/unipress",
|
|
104
104
|
"deps": [
|
|
105
105
|
"@uniweb/build",
|
package/src/versions.js
CHANGED
|
@@ -53,16 +53,86 @@ let resolvedVersions = null
|
|
|
53
53
|
export const REACT_VERSION = '^19.0.0'
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Default pnpm major for generated CI, used only when the project does not
|
|
57
|
+
* say which pnpm it uses (see `resolveCiPnpmVersion`).
|
|
57
58
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
59
|
+
* **This is deliberately 10, not the newest release.** CI must run the same
|
|
60
|
+
* toolchain the developer runs, or it reports failures that have nothing to
|
|
61
|
+
* do with their code. Pinning 11 while projects were on 10 produced exactly
|
|
62
|
+
* that, twice over, on a real repo:
|
|
63
|
+
*
|
|
64
|
+
* - pnpm 11 refuses any dependency published in the last 24 hours
|
|
65
|
+
* (`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`). Publish a package and its
|
|
66
|
+
* consumer's CI fails until the next day.
|
|
67
|
+
* - pnpm 11 changed build-script approval: `onlyBuiltDependencies` in
|
|
68
|
+
* `pnpm-workspace.yaml` no longer suffices, and the install exits 1 with
|
|
69
|
+
* `ERR_PNPM_IGNORED_BUILDS`. Uniweb sites use `sharp` for image
|
|
70
|
+
* processing, so this breaks essentially every site's first CI run.
|
|
71
|
+
*
|
|
72
|
+
* Neither is a bug in pnpm — both are reasonable hardening. They are simply
|
|
73
|
+
* not things a generated workflow should impose on a project that has not
|
|
74
|
+
* opted into that major.
|
|
75
|
+
*
|
|
76
|
+
* A bare major installs the latest patch of that major at CI run time. Before
|
|
77
|
+
* bumping this, check the new major's release notes for install-time policy
|
|
78
|
+
* changes, and update `PNPM_MIN_NODE` in the same commit.
|
|
64
79
|
*/
|
|
65
|
-
export const PNPM_VERSION = '
|
|
80
|
+
export const PNPM_VERSION = '10'
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Minimum Node major each supported pnpm major will run on, from that
|
|
84
|
+
* release's own `engines.node`.
|
|
85
|
+
*
|
|
86
|
+
* pnpm 11 declares `>=22.13` and imports `node:sqlite`, which does not exist
|
|
87
|
+
* before Node 22 — pairing it with Node 20 fails at `pnpm install` with
|
|
88
|
+
* `ERR_UNKNOWN_BUILTIN_MODULE` before the build is attempted. That was the
|
|
89
|
+
* default until 2026-07-21, because the CI node major came from the
|
|
90
|
+
* project's `engines.node` and the workspace template declares `>=20.19`.
|
|
91
|
+
*
|
|
92
|
+
* Verify with `npm view pnpm@<major> engines` when adding an entry.
|
|
93
|
+
*/
|
|
94
|
+
export const PNPM_MIN_NODE = { '10': 18, '11': 22 }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the pnpm major a generated CI workflow should install.
|
|
98
|
+
*
|
|
99
|
+
* `packageManager` (the corepack field) is authoritative when present — it is
|
|
100
|
+
* the project stating which pnpm it is developed and locked against, so CI
|
|
101
|
+
* should honour it rather than impose a different major.
|
|
102
|
+
*
|
|
103
|
+
* `pnpm-lock.yaml`'s `lockfileVersion` is NOT usable as a signal: pnpm 10 and
|
|
104
|
+
* 11 both write `9.0`, so it cannot distinguish them. Checked, not assumed.
|
|
105
|
+
*
|
|
106
|
+
* @param {object|null} rootPkg — the workspace root's parsed package.json.
|
|
107
|
+
* @returns {string} pnpm major, as a string for YAML interpolation.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveCiPnpmVersion(rootPkg) {
|
|
110
|
+
const declared = rootPkg?.packageManager
|
|
111
|
+
const match = typeof declared === 'string' ? declared.match(/^pnpm@(\d+)/) : null
|
|
112
|
+
return match ? match[1] : PNPM_VERSION
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the Node major a generated CI workflow should install.
|
|
117
|
+
*
|
|
118
|
+
* Respects the project's own floor (`engines.node`) but never drops below
|
|
119
|
+
* what the pinned package manager needs. The project's declared minimum is a
|
|
120
|
+
* *lower* bound on what its code needs, not a ceiling — running CI on a newer
|
|
121
|
+
* Node is fine; running it on one the package manager refuses to start on is
|
|
122
|
+
* not.
|
|
123
|
+
*
|
|
124
|
+
* @param {string|null|undefined} enginesNode — the project's `engines.node`.
|
|
125
|
+
* @param {'pnpm'|'npm'|'yarn'} packageManager
|
|
126
|
+
* @param {string} [pnpmVersion] — the resolved pnpm major, when pnpm.
|
|
127
|
+
* @param {string} [fallback='20'] — used when engines.node is absent/unparseable.
|
|
128
|
+
* @returns {string} Node major, as a string for YAML interpolation.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveCiNodeVersion(enginesNode, packageManager, pnpmVersion = PNPM_VERSION, fallback = '20') {
|
|
131
|
+
const match = enginesNode ? String(enginesNode).match(/(\d+)/) : null
|
|
132
|
+
const declared = match ? Number(match[1]) : Number(fallback)
|
|
133
|
+
const floor = packageManager === 'pnpm' ? (PNPM_MIN_NODE[pnpmVersion] ?? 0) : 0
|
|
134
|
+
return String(Math.max(declared, floor))
|
|
135
|
+
}
|
|
66
136
|
|
|
67
137
|
/**
|
|
68
138
|
* Get the CLI's own package.json
|