uniweb 0.8.15 → 0.8.16
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/README.md +8 -8
- package/package.json +19 -6
- package/partials/agents.md +16 -13
- package/partials/components-docs.hbs +2 -2
- package/partials/config-reference.hbs +1 -1
- package/src/commands/add.js +1 -1
- package/src/commands/doctor.js +2 -2
- package/src/index.js +178 -28
- package/src/utils/update-check.js +105 -0
- package/src/versions.js +1 -1
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ After creating your project:
|
|
|
200
200
|
|
|
201
201
|
1. **Explore the structure** — Browse `site/pages/` to see how content is organized. Each page folder contains `page.yml` (metadata) and `.md` files (sections).
|
|
202
202
|
|
|
203
|
-
2. **Generate component docs** — Run `
|
|
203
|
+
2. **Generate component docs** — Run `uniweb docs` to create `COMPONENTS.md` with all available components, their parameters, and presets.
|
|
204
204
|
|
|
205
205
|
3. **Learn the configuration** — Run `uniweb docs site` or `uniweb docs page` for quick reference on configuration options.
|
|
206
206
|
|
|
@@ -267,31 +267,31 @@ Start simple. Add what you need, when you need it:
|
|
|
267
267
|
cd my-site
|
|
268
268
|
|
|
269
269
|
# Add a co-located foundation + site pair
|
|
270
|
-
|
|
270
|
+
uniweb add project blog
|
|
271
271
|
|
|
272
272
|
# Add a second foundation at root
|
|
273
|
-
|
|
273
|
+
uniweb add foundation ui
|
|
274
274
|
|
|
275
275
|
# Add a site wired to a specific foundation
|
|
276
|
-
|
|
276
|
+
uniweb add site docs --foundation ui
|
|
277
277
|
|
|
278
278
|
# Add an extension (auto-wired to the only site)
|
|
279
|
-
|
|
279
|
+
uniweb add extension effects
|
|
280
280
|
|
|
281
281
|
# Scaffold + apply content from an official template
|
|
282
|
-
|
|
282
|
+
uniweb add project marketing --from marketing
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
The workspace grows organically. `add` handles placement, wires dependencies, updates workspace globs, and generates root scripts. The name you provide becomes both the directory name and the package name. Use `--path` to override default placement, or `--project` for explicit co-located layouts.
|
|
286
286
|
|
|
287
|
-
>
|
|
287
|
+
> **Install the CLI globally** with `npm i -g uniweb` for the best experience. You can also use `npx uniweb` or `pnpm uniweb` without a global install.
|
|
288
288
|
|
|
289
289
|
**Or start blank and build up:**
|
|
290
290
|
|
|
291
291
|
```bash
|
|
292
292
|
pnpm create uniweb acme --blank
|
|
293
293
|
cd acme
|
|
294
|
-
|
|
294
|
+
uniweb add project main
|
|
295
295
|
pnpm install
|
|
296
296
|
pnpm dev
|
|
297
297
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.16",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,11 +41,24 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/build": "0.8.14",
|
|
45
|
-
"@uniweb/content-reader": "1.1.4",
|
|
46
|
-
"@uniweb/core": "0.5.10",
|
|
47
44
|
"@uniweb/runtime": "0.6.11",
|
|
48
|
-
"@uniweb/
|
|
49
|
-
"@uniweb/
|
|
45
|
+
"@uniweb/core": "0.5.10",
|
|
46
|
+
"@uniweb/kit": "0.7.11"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@uniweb/build": "0.8.15",
|
|
50
|
+
"@uniweb/semantic-parser": "1.1.6",
|
|
51
|
+
"@uniweb/content-reader": "1.1.4"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@uniweb/build": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@uniweb/content-reader": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"@uniweb/semantic-parser": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
64
|
}
|
package/partials/agents.md
CHANGED
|
@@ -83,7 +83,7 @@ Use `--template <n>` for an official template (`none`, `starter`, `marketing`, `
|
|
|
83
83
|
### Adding a co-located project
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
|
|
86
|
+
uniweb add project docs
|
|
87
87
|
pnpm install
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -92,10 +92,10 @@ This creates `docs/foundation/` + `docs/site/` with package names `docs-foundati
|
|
|
92
92
|
### Adding individual packages
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
uniweb add foundation # First foundation → ./foundation/
|
|
96
|
+
uniweb add foundation ui # Named → ./ui/
|
|
97
|
+
uniweb add site # First site → ./site/
|
|
98
|
+
uniweb add site blog # Named → ./blog/
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The name is both the directory name and the package name. Use `--project <n>` to co-locate under a project directory (e.g., `--project docs` → `docs/foundation/`).
|
|
@@ -103,8 +103,8 @@ The name is both the directory name and the package name. Use `--project <n>` to
|
|
|
103
103
|
### Adding section types
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
uniweb add section Hero
|
|
107
|
+
uniweb add section Hero --foundation ui # When multiple foundations exist
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
Creates `src/sections/Hero/index.jsx` and `meta.js` with a minimal CCA-proper starter. The dev server picks it up automatically — no build or install needed.
|
|
@@ -340,9 +340,9 @@ Check out [this](/a) link. ← inline → stays in paragraphs as <a> tag
|
|
|
340
340
|
This is [less important]{muted} context.
|
|
341
341
|
```
|
|
342
342
|
|
|
343
|
-
`accent` (colored + bold) and `muted` (subtle) adapt to context automatically. Components receive HTML strings with spans applied: `<span accent="true">faster</span>`.
|
|
343
|
+
`accent` (link-colored + bold), `callout` (accent-colored + bold), and `muted` (subtle) are built-in defaults that adapt to context automatically. Components receive HTML strings with spans applied: `<span accent="true">faster</span>`.
|
|
344
344
|
|
|
345
|
-
Sites can define additional named styles in `theme.yml`'s `inline:` section.
|
|
345
|
+
Sites can override these or define additional named styles in `theme.yml`'s `inline:` section.
|
|
346
346
|
|
|
347
347
|
### Fenced Code in Content
|
|
348
348
|
|
|
@@ -676,6 +676,9 @@ inline:
|
|
|
676
676
|
accent:
|
|
677
677
|
color: var(--link)
|
|
678
678
|
font-weight: '600'
|
|
679
|
+
callout:
|
|
680
|
+
color: var(--accent)
|
|
681
|
+
font-weight: '600'
|
|
679
682
|
|
|
680
683
|
vars:
|
|
681
684
|
header-height: 5rem
|
|
@@ -1282,9 +1285,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
|
|
|
1282
1285
|
|
|
1283
1286
|
**Content not appearing as expected?**
|
|
1284
1287
|
```bash
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
+
uniweb inspect pages/home/hero.md # Single section
|
|
1289
|
+
uniweb inspect pages/home/ # Whole page
|
|
1290
|
+
uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
1288
1291
|
```
|
|
1289
1292
|
|
|
1290
1293
|
## Learning from Official Templates
|
|
@@ -1292,7 +1295,7 @@ pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
|
1292
1295
|
When you're unsure how to implement a pattern — data fetching, i18n, layouts, insets, theming — install an official template as a reference project in your workspace:
|
|
1293
1296
|
|
|
1294
1297
|
```bash
|
|
1295
|
-
|
|
1298
|
+
uniweb add project marketing --from marketing
|
|
1296
1299
|
pnpm install
|
|
1297
1300
|
```
|
|
1298
1301
|
|
|
@@ -4,10 +4,10 @@ Generate up-to-date documentation for all foundation components:
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
# From foundation directory
|
|
7
|
-
|
|
7
|
+
uniweb docs
|
|
8
8
|
|
|
9
9
|
# Or from site directory (auto-detects linked foundation)
|
|
10
|
-
cd site &&
|
|
10
|
+
cd site && uniweb docs
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
This creates `COMPONENTS.md` with details on each component's parameters, presets, and content elements. The documentation is generated from component `meta.js` files, so it's always current.
|
package/src/commands/add.js
CHANGED
|
@@ -352,7 +352,7 @@ async function addSite(rootDir, projectName, opts, pm = 'pnpm') {
|
|
|
352
352
|
}, {
|
|
353
353
|
onProgress: (msg) => info(` ${msg}`),
|
|
354
354
|
})
|
|
355
|
-
log(` ${colors.yellow}⚠ No foundation wired. Add one later with:
|
|
355
|
+
log(` ${colors.yellow}⚠ No foundation wired. Add one later with: uniweb add foundation${colors.reset}`)
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
// Apply template content if --from specified
|
package/src/commands/doctor.js
CHANGED
|
@@ -379,7 +379,7 @@ export async function doctor(args = []) {
|
|
|
379
379
|
message: `Foundation not built: ${matchingFoundation.name}`
|
|
380
380
|
})
|
|
381
381
|
warn(`Foundation not built yet`)
|
|
382
|
-
log(` ${colors.dim}Run:
|
|
382
|
+
log(` ${colors.dim}Run: uniweb build${colors.reset}`)
|
|
383
383
|
} else {
|
|
384
384
|
success(`Foundation built: dist/foundation.js exists`)
|
|
385
385
|
}
|
|
@@ -431,7 +431,7 @@ export async function doctor(args = []) {
|
|
|
431
431
|
message: `Extension not built: ${ext.name}`
|
|
432
432
|
})
|
|
433
433
|
warn(`Extension not built yet`)
|
|
434
|
-
log(` ${colors.dim}Run:
|
|
434
|
+
log(` ${colors.dim}Run: uniweb build${colors.reset}`)
|
|
435
435
|
} else {
|
|
436
436
|
success(`Extension built: dist/foundation.js exists`)
|
|
437
437
|
}
|
package/src/index.js
CHANGED
|
@@ -5,20 +5,27 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Scaffolds new Uniweb sites and foundations, builds projects, and generates docs.
|
|
7
7
|
*
|
|
8
|
+
* Install globally:
|
|
9
|
+
* npm i -g uniweb
|
|
10
|
+
*
|
|
8
11
|
* Usage:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* uniweb create [project-name]
|
|
13
|
+
* uniweb create --template marketing
|
|
14
|
+
* uniweb add foundation [name]
|
|
15
|
+
* uniweb build
|
|
16
|
+
* uniweb docs
|
|
17
|
+
*
|
|
18
|
+
* Global install delegation:
|
|
19
|
+
* When installed globally, project-bound commands (build, docs, etc.) are
|
|
20
|
+
* delegated to the project-local CLI if one exists in node_modules. This
|
|
21
|
+
* ensures version alignment between the CLI and @uniweb/build.
|
|
14
22
|
*/
|
|
15
23
|
|
|
16
|
-
import { existsSync } from 'node:fs'
|
|
17
|
-
import { execSync } from 'node:child_process'
|
|
18
|
-
import { resolve, join, relative } from 'node:path'
|
|
24
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
25
|
+
import { execSync, spawn as spawnChild } from 'node:child_process'
|
|
26
|
+
import { resolve, join, relative, dirname } from 'node:path'
|
|
27
|
+
import { fileURLToPath } from 'node:url'
|
|
19
28
|
import prompts from 'prompts'
|
|
20
|
-
import { build } from './commands/build.js'
|
|
21
|
-
import { docs } from './commands/docs.js'
|
|
22
29
|
import { doctor } from './commands/doctor.js'
|
|
23
30
|
import { i18n } from './commands/i18n.js'
|
|
24
31
|
import { inspect } from './commands/inspect.js'
|
|
@@ -80,6 +87,108 @@ function title(message) {
|
|
|
80
87
|
console.log(`\n${colors.cyan}${colors.bright}${message}${colors.reset}\n`)
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
// CLI version (read once, lazily)
|
|
91
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
92
|
+
let _cliVersion = null
|
|
93
|
+
function getCliVersion() {
|
|
94
|
+
if (!_cliVersion) {
|
|
95
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
|
|
96
|
+
_cliVersion = pkg.version
|
|
97
|
+
}
|
|
98
|
+
return _cliVersion
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Commands that always run from the global CLI (no project context needed)
|
|
103
|
+
*/
|
|
104
|
+
const STANDALONE_COMMANDS = new Set([
|
|
105
|
+
'create', '--help', '-h', '--version', '-v', 'login',
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if this CLI is running from a global install.
|
|
110
|
+
* When installed globally, process.argv[1] points outside any node_modules.
|
|
111
|
+
* When run via npx or as a local dependency, it's inside node_modules.
|
|
112
|
+
*/
|
|
113
|
+
function isGlobalInstall() {
|
|
114
|
+
const scriptPath = process.argv[1]
|
|
115
|
+
if (!scriptPath) return false
|
|
116
|
+
// Normalize path separators for Windows compatibility
|
|
117
|
+
return !scriptPath.split('/').includes('node_modules') &&
|
|
118
|
+
!scriptPath.split('\\').includes('node_modules')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find the project-local CLI entry point, if one exists.
|
|
123
|
+
* Walks up from cwd looking for node_modules/uniweb/src/index.js.
|
|
124
|
+
*/
|
|
125
|
+
function findLocalCli() {
|
|
126
|
+
let dir = process.cwd()
|
|
127
|
+
while (true) {
|
|
128
|
+
const localCli = join(dir, 'node_modules', 'uniweb', 'src', 'index.js')
|
|
129
|
+
if (existsSync(localCli)) return localCli
|
|
130
|
+
const parent = dirname(dir)
|
|
131
|
+
if (parent === dir) break
|
|
132
|
+
dir = parent
|
|
133
|
+
}
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Delegate execution to the project-local CLI.
|
|
139
|
+
* Spawns the local CLI with the same arguments and inherits stdio.
|
|
140
|
+
* Warns if the local version differs from the global version.
|
|
141
|
+
*/
|
|
142
|
+
function delegateToLocal(localCliPath) {
|
|
143
|
+
// Check for version mismatch between global and local CLI
|
|
144
|
+
try {
|
|
145
|
+
const localPkgPath = join(dirname(localCliPath), '..', 'package.json')
|
|
146
|
+
const localPkg = JSON.parse(readFileSync(localPkgPath, 'utf8'))
|
|
147
|
+
const globalVersion = getCliVersion()
|
|
148
|
+
if (localPkg.version && localPkg.version !== globalVersion) {
|
|
149
|
+
const yellow = '\x1b[33m'
|
|
150
|
+
const dim = '\x1b[2m'
|
|
151
|
+
const reset = '\x1b[0m'
|
|
152
|
+
console.error(`${yellow}Note:${reset} Global CLI is ${dim}${globalVersion}${reset}, project has ${dim}${localPkg.version}${reset} ${dim}(using project version)${reset}`)
|
|
153
|
+
}
|
|
154
|
+
} catch { /* ignore — version check is best-effort */ }
|
|
155
|
+
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const child = spawnChild(
|
|
158
|
+
process.execPath,
|
|
159
|
+
[localCliPath, ...process.argv.slice(2)],
|
|
160
|
+
{ stdio: 'inherit' }
|
|
161
|
+
)
|
|
162
|
+
child.on('close', (code) => process.exit(code ?? 0))
|
|
163
|
+
child.on('error', reject)
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Import a command module that may depend on @uniweb/build.
|
|
169
|
+
* Provides a helpful error when the dependency can't be resolved
|
|
170
|
+
* (e.g., running a project-bound command from a global install
|
|
171
|
+
* outside a project directory).
|
|
172
|
+
*/
|
|
173
|
+
async function importProjectCommand(modulePath) {
|
|
174
|
+
try {
|
|
175
|
+
return await import(modulePath)
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND' && err.message?.includes('@uniweb/')) {
|
|
178
|
+
error('This command must be run from inside a Uniweb project.')
|
|
179
|
+
log('')
|
|
180
|
+
log(`Make sure you're in a project directory with dependencies installed:`)
|
|
181
|
+
log(` ${colors.cyan}cd your-project${colors.reset}`)
|
|
182
|
+
log(` ${colors.cyan}npm install${colors.reset}`)
|
|
183
|
+
log('')
|
|
184
|
+
log(`Or create a new project:`)
|
|
185
|
+
log(` ${colors.cyan}uniweb create my-project${colors.reset}`)
|
|
186
|
+
process.exit(1)
|
|
187
|
+
}
|
|
188
|
+
throw err
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
83
192
|
/**
|
|
84
193
|
* Create a project using the new package template flow (default)
|
|
85
194
|
*/
|
|
@@ -306,21 +415,55 @@ async function main() {
|
|
|
306
415
|
const command = args[0]
|
|
307
416
|
const pm = detectPackageManager()
|
|
308
417
|
|
|
418
|
+
// Handle --version / -v
|
|
419
|
+
if (command === '--version' || command === '-v') {
|
|
420
|
+
console.log(`uniweb ${getCliVersion()}`)
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Global install launcher: delegate project-bound commands to local CLI
|
|
425
|
+
const global = isGlobalInstall()
|
|
426
|
+
if (global && command && !STANDALONE_COMMANDS.has(command)) {
|
|
427
|
+
const localCli = findLocalCli()
|
|
428
|
+
if (localCli) {
|
|
429
|
+
await delegateToLocal(localCli)
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
// No local CLI found — fall through and try to run the command directly.
|
|
433
|
+
// Commands that need @uniweb/build will get a helpful error via importProjectCommand().
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Start non-blocking update check for global installs
|
|
437
|
+
let showUpdateNotification = () => {}
|
|
438
|
+
if (global) {
|
|
439
|
+
try {
|
|
440
|
+
const { startUpdateCheck } = await import('./utils/update-check.js')
|
|
441
|
+
showUpdateNotification = startUpdateCheck(getCliVersion())
|
|
442
|
+
} catch {
|
|
443
|
+
// Update check is optional — don't fail if the module is missing
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
309
447
|
// Show help
|
|
310
448
|
if (!command || command === '--help' || command === '-h') {
|
|
311
449
|
showHelp()
|
|
450
|
+
await showUpdateNotification()
|
|
312
451
|
return
|
|
313
452
|
}
|
|
314
453
|
|
|
315
|
-
// Handle build command
|
|
454
|
+
// Handle build command (dynamic import — depends on @uniweb/build)
|
|
316
455
|
if (command === 'build') {
|
|
456
|
+
const { build } = await importProjectCommand('./commands/build.js')
|
|
317
457
|
await build(args.slice(1))
|
|
458
|
+
await showUpdateNotification()
|
|
318
459
|
return
|
|
319
460
|
}
|
|
320
461
|
|
|
321
|
-
// Handle docs command
|
|
462
|
+
// Handle docs command (dynamic import — depends on @uniweb/build)
|
|
322
463
|
if (command === 'docs') {
|
|
464
|
+
const { docs } = await importProjectCommand('./commands/docs.js')
|
|
323
465
|
await docs(args.slice(1))
|
|
466
|
+
await showUpdateNotification()
|
|
324
467
|
return
|
|
325
468
|
}
|
|
326
469
|
|
|
@@ -616,14 +759,16 @@ async function main() {
|
|
|
616
759
|
log(` ${colors.cyan}${runCmd(pm, 'dev')}${colors.reset}`)
|
|
617
760
|
}
|
|
618
761
|
log('')
|
|
762
|
+
|
|
763
|
+
await showUpdateNotification()
|
|
619
764
|
}
|
|
620
765
|
|
|
621
766
|
function showHelp() {
|
|
622
767
|
log(`
|
|
623
|
-
${colors.cyan}${colors.bright}Uniweb CLI${colors.reset}
|
|
768
|
+
${colors.cyan}${colors.bright}Uniweb CLI${colors.reset} ${colors.dim}v${getCliVersion()}${colors.reset}
|
|
624
769
|
|
|
625
770
|
${colors.bright}Usage:${colors.reset}
|
|
626
|
-
|
|
771
|
+
uniweb <command> [options]
|
|
627
772
|
|
|
628
773
|
${colors.bright}Commands:${colors.reset}
|
|
629
774
|
create [name] Create a new project
|
|
@@ -654,6 +799,7 @@ ${colors.bright}Add Subcommands:${colors.reset}
|
|
|
654
799
|
add section <name> Add a section type to a foundation (--foundation)
|
|
655
800
|
|
|
656
801
|
${colors.bright}Global Options:${colors.reset}
|
|
802
|
+
--version, -v Show version
|
|
657
803
|
--non-interactive Fail with usage info instead of prompting
|
|
658
804
|
Auto-detected when CI=true or no TTY (pipes, agents)
|
|
659
805
|
|
|
@@ -720,22 +866,26 @@ ${colors.bright}Template Types:${colors.reset}
|
|
|
720
866
|
https://github.com/user/repo GitHub URL
|
|
721
867
|
|
|
722
868
|
${colors.bright}Examples:${colors.reset}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
869
|
+
uniweb create my-project # Foundation + site + starter content
|
|
870
|
+
uniweb create my-project --template none # Foundation + site, no content
|
|
871
|
+
uniweb create my-project --blank # Empty workspace
|
|
872
|
+
uniweb create my-project --template marketing # Official template
|
|
873
|
+
uniweb create my-project --template ./my-template # Local template
|
|
728
874
|
|
|
729
875
|
cd my-project
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
cd foundation &&
|
|
876
|
+
uniweb add project docs # Add docs/foundation/ + docs/site/
|
|
877
|
+
uniweb add project docs --from academic # Co-located pair + academic content
|
|
878
|
+
uniweb add foundation # Add foundation at root
|
|
879
|
+
uniweb add site blog --foundation marketing # Add site wired to marketing
|
|
880
|
+
uniweb add extension effects --site site # Add extensions/effects/
|
|
881
|
+
|
|
882
|
+
uniweb build
|
|
883
|
+
uniweb build --target foundation
|
|
884
|
+
cd foundation && uniweb docs # Generate COMPONENTS.md
|
|
885
|
+
|
|
886
|
+
${colors.bright}Install:${colors.reset}
|
|
887
|
+
npm i -g uniweb Global install (recommended)
|
|
888
|
+
npx uniweb <command> Run without installing
|
|
739
889
|
`)
|
|
740
890
|
}
|
|
741
891
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight Update Notification
|
|
3
|
+
*
|
|
4
|
+
* Checks the npm registry for newer versions of the CLI.
|
|
5
|
+
* Runs at most once per day, caches results in ~/.uniweb/update-check.json.
|
|
6
|
+
* Uses Node 20+ built-in fetch — no external dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { homedir } from 'node:os'
|
|
10
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
|
|
13
|
+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000 // 1 day
|
|
14
|
+
const STATE_DIR = join(homedir(), '.uniweb')
|
|
15
|
+
const STATE_FILE = join(STATE_DIR, 'update-check.json')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compare two semver strings.
|
|
19
|
+
* Returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
20
|
+
*/
|
|
21
|
+
function compareSemver(a, b) {
|
|
22
|
+
const pa = a.split('.').map(Number)
|
|
23
|
+
const pb = b.split('.').map(Number)
|
|
24
|
+
for (let i = 0; i < 3; i++) {
|
|
25
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1
|
|
26
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1
|
|
27
|
+
}
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read cached update check state.
|
|
33
|
+
*/
|
|
34
|
+
function readState() {
|
|
35
|
+
try {
|
|
36
|
+
if (existsSync(STATE_FILE)) {
|
|
37
|
+
return JSON.parse(readFileSync(STATE_FILE, 'utf8'))
|
|
38
|
+
}
|
|
39
|
+
} catch { /* ignore corrupt cache */ }
|
|
40
|
+
return {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Write update check state to disk.
|
|
45
|
+
*/
|
|
46
|
+
function writeState(state) {
|
|
47
|
+
try {
|
|
48
|
+
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true })
|
|
49
|
+
writeFileSync(STATE_FILE, JSON.stringify(state))
|
|
50
|
+
} catch { /* ignore write errors */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Print update notification to stderr (doesn't interfere with piped output).
|
|
55
|
+
*/
|
|
56
|
+
function printNotification(current, latest) {
|
|
57
|
+
const yellow = '\x1b[33m'
|
|
58
|
+
const cyan = '\x1b[36m'
|
|
59
|
+
const dim = '\x1b[2m'
|
|
60
|
+
const reset = '\x1b[0m'
|
|
61
|
+
console.error('')
|
|
62
|
+
console.error(`${yellow}Update available:${reset} ${dim}${current}${reset} → ${cyan}${latest}${reset}`)
|
|
63
|
+
console.error(`${dim}Run${reset} npm i -g uniweb ${dim}to update${reset}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Start a non-blocking update check.
|
|
68
|
+
*
|
|
69
|
+
* Returns a function that, when called (optionally awaited), prints
|
|
70
|
+
* the notification if a newer version was found.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} currentVersion - The currently running CLI version
|
|
73
|
+
* @returns {Function} Call at the end of command execution to show notification
|
|
74
|
+
*/
|
|
75
|
+
export function startUpdateCheck(currentVersion) {
|
|
76
|
+
let notification = null
|
|
77
|
+
const state = readState()
|
|
78
|
+
|
|
79
|
+
// Use cached result if checked recently
|
|
80
|
+
if (state.lastCheck && (Date.now() - state.lastCheck) < CHECK_INTERVAL) {
|
|
81
|
+
if (state.latestVersion && compareSemver(state.latestVersion, currentVersion) > 0) {
|
|
82
|
+
notification = state.latestVersion
|
|
83
|
+
}
|
|
84
|
+
return () => {
|
|
85
|
+
if (notification) printNotification(currentVersion, notification)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Background fetch (non-blocking)
|
|
90
|
+
const fetchPromise = fetch('https://registry.npmjs.org/uniweb/latest')
|
|
91
|
+
.then(r => r.json())
|
|
92
|
+
.then(data => {
|
|
93
|
+
const latest = data.version
|
|
94
|
+
writeState({ lastCheck: Date.now(), latestVersion: latest })
|
|
95
|
+
if (compareSemver(latest, currentVersion) > 0) {
|
|
96
|
+
notification = latest
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.catch(() => { /* network error — ignore silently */ })
|
|
100
|
+
|
|
101
|
+
return async () => {
|
|
102
|
+
await fetchPromise
|
|
103
|
+
if (notification) printNotification(currentVersion, notification)
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/versions.js
CHANGED
|
@@ -61,7 +61,7 @@ export function getResolvedVersions() {
|
|
|
61
61
|
if (resolvedVersions) return resolvedVersions
|
|
62
62
|
|
|
63
63
|
const pkg = getCliPackageJson()
|
|
64
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
64
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies }
|
|
65
65
|
|
|
66
66
|
// All @uniweb/* packages are now direct dependencies of the CLI.
|
|
67
67
|
// When publishing with pnpm, workspace:* gets resolved to actual versions.
|