uniweb 0.8.3 → 0.8.4
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 +48 -2
- package/src/commands/docs.js +7 -0
- package/src/index.js +28 -1
- package/src/utils/interactive.js +53 -0
- package/templates/site/theme.yml +99 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/
|
|
45
|
-
"@uniweb/build": "0.8.3",
|
|
44
|
+
"@uniweb/build": "0.8.4",
|
|
46
45
|
"@uniweb/core": "0.5.4",
|
|
46
|
+
"@uniweb/runtime": "0.6.4",
|
|
47
47
|
"@uniweb/kit": "0.7.3"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/commands/add.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { validatePackageName, getExistingPackageNames, resolveUniqueName } from '../utils/names.js'
|
|
26
26
|
import { findWorkspaceRoot } from '../utils/workspace.js'
|
|
27
27
|
import { detectPackageManager, filterCmd, installCmd } from '../utils/pm.js'
|
|
28
|
+
import { isNonInteractive, getCliPrefix, stripNonInteractiveFlag, formatOptions } from '../utils/interactive.js'
|
|
28
29
|
import { resolveTemplate } from '../templates/index.js'
|
|
29
30
|
import { validateTemplate } from '../templates/validator.js'
|
|
30
31
|
import { getVersionsForTemplates } from '../versions.js'
|
|
@@ -91,13 +92,17 @@ function parseArgs(args) {
|
|
|
91
92
|
/**
|
|
92
93
|
* Main add command handler
|
|
93
94
|
*/
|
|
94
|
-
export async function add(
|
|
95
|
+
export async function add(rawArgs) {
|
|
96
|
+
const nonInteractive = isNonInteractive(rawArgs)
|
|
97
|
+
const args = stripNonInteractiveFlag(rawArgs)
|
|
98
|
+
|
|
95
99
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
96
100
|
showAddHelp()
|
|
97
101
|
return
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
const pm = detectPackageManager()
|
|
105
|
+
const prefix = getCliPrefix()
|
|
101
106
|
|
|
102
107
|
// Find workspace root
|
|
103
108
|
const rootDir = findWorkspaceRoot()
|
|
@@ -110,6 +115,18 @@ export async function add(args) {
|
|
|
110
115
|
// Interactive subcommand chooser when no args given
|
|
111
116
|
let parsed
|
|
112
117
|
if (!args.length || (args[0] && args[0].startsWith('--'))) {
|
|
118
|
+
if (nonInteractive) {
|
|
119
|
+
error(`Missing subcommand.\n`)
|
|
120
|
+
log(formatOptions([
|
|
121
|
+
{ label: 'foundation', description: 'Component library' },
|
|
122
|
+
{ label: 'site', description: 'Content site' },
|
|
123
|
+
{ label: 'extension', description: 'Additional component package' },
|
|
124
|
+
]))
|
|
125
|
+
log('')
|
|
126
|
+
log(`Usage: ${prefix} add <foundation|site|extension> [name]`)
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
const response = await prompts({
|
|
114
131
|
type: 'select',
|
|
115
132
|
name: 'subcommand',
|
|
@@ -171,6 +188,12 @@ async function addFoundation(rootDir, projectName, opts, pm = 'pnpm') {
|
|
|
171
188
|
|
|
172
189
|
// Interactive name prompt when name not provided and no --path
|
|
173
190
|
if (!name && !opts.path) {
|
|
191
|
+
if (isNonInteractive(process.argv)) {
|
|
192
|
+
error(`Missing foundation name.\n`)
|
|
193
|
+
log(`Usage: ${getCliPrefix()} add foundation <name>`)
|
|
194
|
+
process.exit(1)
|
|
195
|
+
}
|
|
196
|
+
|
|
174
197
|
const foundations = await discoverFoundations(rootDir)
|
|
175
198
|
const hasDefault = foundations.length === 0 && !existsSync(join(rootDir, 'foundation'))
|
|
176
199
|
const response = await prompts({
|
|
@@ -251,6 +274,12 @@ async function addSite(rootDir, projectName, opts, pm = 'pnpm') {
|
|
|
251
274
|
|
|
252
275
|
// Interactive name prompt when name not provided and no --path
|
|
253
276
|
if (!name && !opts.path) {
|
|
277
|
+
if (isNonInteractive(process.argv)) {
|
|
278
|
+
error(`Missing site name.\n`)
|
|
279
|
+
log(`Usage: ${getCliPrefix()} add site <name>`)
|
|
280
|
+
process.exit(1)
|
|
281
|
+
}
|
|
282
|
+
|
|
254
283
|
const existingSites = await discoverSites(rootDir)
|
|
255
284
|
const hasDefault = existingSites.length === 0 && !existsSync(join(rootDir, 'site'))
|
|
256
285
|
const response = await prompts({
|
|
@@ -371,6 +400,12 @@ async function addExtension(rootDir, projectName, opts, pm = 'pnpm') {
|
|
|
371
400
|
|
|
372
401
|
// Interactive name prompt when name not provided
|
|
373
402
|
if (!name) {
|
|
403
|
+
if (isNonInteractive(process.argv)) {
|
|
404
|
+
error(`Missing extension name.\n`)
|
|
405
|
+
log(`Usage: ${getCliPrefix()} add extension <name>`)
|
|
406
|
+
process.exit(1)
|
|
407
|
+
}
|
|
408
|
+
|
|
374
409
|
const response = await prompts({
|
|
375
410
|
type: 'text',
|
|
376
411
|
name: 'name',
|
|
@@ -529,7 +564,18 @@ async function resolveFoundation(rootDir, foundationFlag) {
|
|
|
529
564
|
return foundations[0]
|
|
530
565
|
}
|
|
531
566
|
|
|
532
|
-
// Multiple foundations — prompt
|
|
567
|
+
// Multiple foundations — prompt (or fail in non-interactive mode)
|
|
568
|
+
if (isNonInteractive(process.argv)) {
|
|
569
|
+
error(`Multiple foundations found. Specify which to use:\n`)
|
|
570
|
+
log(formatOptions(foundations.map(f => ({
|
|
571
|
+
label: f.name,
|
|
572
|
+
description: f.path,
|
|
573
|
+
}))))
|
|
574
|
+
log('')
|
|
575
|
+
log(`Usage: ${getCliPrefix()} add site <name> --foundation <name>`)
|
|
576
|
+
process.exit(1)
|
|
577
|
+
}
|
|
578
|
+
|
|
533
579
|
const response = await prompts({
|
|
534
580
|
type: 'select',
|
|
535
581
|
name: 'foundation',
|
package/src/commands/docs.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
findFoundations,
|
|
30
30
|
promptSelect,
|
|
31
31
|
} from '../utils/workspace.js'
|
|
32
|
+
import { isNonInteractive, getCliPrefix, formatOptions } from '../utils/interactive.js'
|
|
32
33
|
|
|
33
34
|
// Colors for terminal output
|
|
34
35
|
const colors = {
|
|
@@ -394,6 +395,12 @@ async function generateComponentDocs(args) {
|
|
|
394
395
|
if (foundations.length === 1) {
|
|
395
396
|
targetFoundation = foundations[0]
|
|
396
397
|
info(`Found foundation: ${targetFoundation}`)
|
|
398
|
+
} else if (isNonInteractive(process.argv)) {
|
|
399
|
+
error(`Multiple foundations found. Specify which to target:\n`)
|
|
400
|
+
log(formatOptions(foundations.map(f => ({ label: f, description: '' }))))
|
|
401
|
+
log('')
|
|
402
|
+
log(`Usage: ${getCliPrefix()} docs --target <path>`)
|
|
403
|
+
process.exit(1)
|
|
397
404
|
} else {
|
|
398
405
|
log(`${colors.dim}Multiple foundations found in workspace.${colors.reset}\n`)
|
|
399
406
|
targetFoundation = await promptSelect('Select foundation:', foundations)
|
package/src/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import { validateTemplate } from './templates/validator.js'
|
|
30
30
|
import { scaffoldWorkspace, scaffoldFoundation, scaffoldSite, applyContent, applyStarter, mergeTemplateDependencies } from './utils/scaffold.js'
|
|
31
31
|
import { detectPackageManager, filterCmd, installCmd, runCmd } from './utils/pm.js'
|
|
32
|
+
import { isNonInteractive, getCliPrefix, stripNonInteractiveFlag, formatOptions } from './utils/interactive.js'
|
|
32
33
|
|
|
33
34
|
// Colors for terminal output
|
|
34
35
|
const colors = {
|
|
@@ -288,7 +289,9 @@ function computeFoundationFilePath(sitePath, foundationPath) {
|
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
async function main() {
|
|
291
|
-
const
|
|
292
|
+
const rawArgs = process.argv.slice(2)
|
|
293
|
+
const nonInteractive = isNonInteractive(rawArgs)
|
|
294
|
+
const args = stripNonInteractiveFlag(rawArgs)
|
|
292
295
|
const command = args[0]
|
|
293
296
|
const pm = detectPackageManager()
|
|
294
297
|
|
|
@@ -369,6 +372,26 @@ async function main() {
|
|
|
369
372
|
projectName = null
|
|
370
373
|
}
|
|
371
374
|
|
|
375
|
+
const prefix = getCliPrefix()
|
|
376
|
+
|
|
377
|
+
// Non-interactive: fail with actionable message instead of prompting
|
|
378
|
+
if (nonInteractive && !projectName) {
|
|
379
|
+
error(`Missing project name.\n`)
|
|
380
|
+
log(`Usage: ${prefix} create <project-name> [--template <name>]`)
|
|
381
|
+
process.exit(1)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (nonInteractive && !templateType) {
|
|
385
|
+
error(`Missing --template flag. Available templates:\n`)
|
|
386
|
+
log(formatOptions(TEMPLATE_CHOICES.map(c => ({
|
|
387
|
+
label: c.value,
|
|
388
|
+
description: c.description,
|
|
389
|
+
}))))
|
|
390
|
+
log('')
|
|
391
|
+
log(`Usage: ${prefix} create ${projectName || '<project-name>'} --template <name>`)
|
|
392
|
+
process.exit(1)
|
|
393
|
+
}
|
|
394
|
+
|
|
372
395
|
// Interactive prompts
|
|
373
396
|
const response = await prompts([
|
|
374
397
|
{
|
|
@@ -539,6 +562,10 @@ ${colors.bright}Add Subcommands:${colors.reset}
|
|
|
539
562
|
add site [name] Add a site (--from, --foundation, --path, --project)
|
|
540
563
|
add extension <name> Add an extension (--from, --site, --path)
|
|
541
564
|
|
|
565
|
+
${colors.bright}Global Options:${colors.reset}
|
|
566
|
+
--non-interactive Fail with usage info instead of prompting
|
|
567
|
+
Auto-detected when CI=true or no TTY (pipes, agents)
|
|
568
|
+
|
|
542
569
|
${colors.bright}Build Options:${colors.reset}
|
|
543
570
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
544
571
|
--prerender Force pre-rendering (overrides site.yml)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-Interactive Mode Detection
|
|
3
|
+
*
|
|
4
|
+
* Detects when the CLI is running without a TTY (AI agents, CI, piped input)
|
|
5
|
+
* and provides helpers for printing actionable error messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect if the CLI is running in non-interactive mode.
|
|
10
|
+
* True when --non-interactive flag, CI env var, or no TTY.
|
|
11
|
+
* @param {string[]} args - Command line arguments
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function isNonInteractive(args) {
|
|
15
|
+
if (args.includes('--non-interactive')) return true
|
|
16
|
+
if (process.env.CI) return true
|
|
17
|
+
if (!process.stdin.isTTY) return true
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the CLI invocation prefix to use in suggested commands.
|
|
23
|
+
* Mirrors however the user actually ran the CLI.
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function getCliPrefix() {
|
|
27
|
+
const ua = process.env.npm_config_user_agent || ''
|
|
28
|
+
if (ua.startsWith('pnpm/')) return 'pnpm uniweb'
|
|
29
|
+
if (ua.startsWith('npm/')) return 'npx uniweb'
|
|
30
|
+
return 'uniweb'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Strip --non-interactive from an args array so it doesn't interfere
|
|
35
|
+
* with positional argument parsing.
|
|
36
|
+
* @param {string[]} args
|
|
37
|
+
* @returns {string[]}
|
|
38
|
+
*/
|
|
39
|
+
export function stripNonInteractiveFlag(args) {
|
|
40
|
+
return args.filter(a => a !== '--non-interactive')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format a list of options with aligned descriptions for terminal output.
|
|
45
|
+
* @param {{ label: string, description: string }[]} options
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function formatOptions(options) {
|
|
49
|
+
const maxLen = Math.max(...options.map(o => o.label.length))
|
|
50
|
+
return options
|
|
51
|
+
.map(o => ` ${o.label.padEnd(maxLen + 3)}${o.description}`)
|
|
52
|
+
.join('\n')
|
|
53
|
+
}
|
package/templates/site/theme.yml
CHANGED
|
@@ -1 +1,99 @@
|
|
|
1
|
-
|
|
1
|
+
# Theme Configuration
|
|
2
|
+
# Controls colors, typography, and visual style for the site.
|
|
3
|
+
# Only set what you want to change — everything has sensible defaults.
|
|
4
|
+
|
|
5
|
+
# ─── Colors ────────────────────────────────────────────────────────────────────
|
|
6
|
+
# Palette colors generate shades (50–950) used by semantic tokens.
|
|
7
|
+
# Values: any CSS color (hex, rgb, hsl, oklch).
|
|
8
|
+
|
|
9
|
+
colors:
|
|
10
|
+
primary: '#3b82f6' # Brand color — buttons, links, focus rings
|
|
11
|
+
# secondary: '#64748b' # Secondary actions
|
|
12
|
+
# accent: '#8b5cf6' # Highlights, decorative elements
|
|
13
|
+
# neutral: stone # Gray family — stone, zinc, gray, slate, neutral
|
|
14
|
+
|
|
15
|
+
# ─── Fonts ─────────────────────────────────────────────────────────────────────
|
|
16
|
+
# Font families for text, headings, and code blocks.
|
|
17
|
+
# Default: system-ui stack. Uncomment to use custom fonts.
|
|
18
|
+
|
|
19
|
+
# fonts:
|
|
20
|
+
# heading: '"Inter", system-ui, sans-serif'
|
|
21
|
+
# body: '"Inter", system-ui, sans-serif'
|
|
22
|
+
# mono: '"JetBrains Mono", ui-monospace, monospace'
|
|
23
|
+
# import:
|
|
24
|
+
# - url: https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap
|
|
25
|
+
# - url: https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap
|
|
26
|
+
|
|
27
|
+
# ─── Page Background ──────────────────────────────────────────────────────────
|
|
28
|
+
# Background applied to the page body. Any CSS background value.
|
|
29
|
+
|
|
30
|
+
# background: '#fafaf9'
|
|
31
|
+
# background: 'linear-gradient(to bottom, #fafaf9, white)'
|
|
32
|
+
|
|
33
|
+
# ─── Appearance ────────────────────────────────────────────────────────────────
|
|
34
|
+
# Color scheme support. Simple value: light, dark, or system.
|
|
35
|
+
# Or use the expanded form for more control.
|
|
36
|
+
|
|
37
|
+
# appearance: light
|
|
38
|
+
# appearance:
|
|
39
|
+
# default: light
|
|
40
|
+
# schemes: [light, dark]
|
|
41
|
+
# allowToggle: true
|
|
42
|
+
# respectSystemPreference: true
|
|
43
|
+
|
|
44
|
+
# ─── Context Overrides ─────────────────────────────────────────────────────────
|
|
45
|
+
# Sections declare a context (light, medium, dark) in frontmatter.
|
|
46
|
+
# Each context maps semantic tokens to palette values.
|
|
47
|
+
# Override individual tokens here — unlisted tokens keep their defaults.
|
|
48
|
+
#
|
|
49
|
+
# Available tokens:
|
|
50
|
+
# Surfaces: section, card, muted
|
|
51
|
+
# Text: body, heading, subtle
|
|
52
|
+
# Border: border, ring
|
|
53
|
+
# Links: link, link-hover
|
|
54
|
+
# Actions: primary, primary-foreground, primary-hover
|
|
55
|
+
# secondary, secondary-foreground, secondary-hover
|
|
56
|
+
# Status: success, warning, error, info (+ -subtle variants)
|
|
57
|
+
|
|
58
|
+
# contexts:
|
|
59
|
+
# light:
|
|
60
|
+
# section: white
|
|
61
|
+
# medium:
|
|
62
|
+
# section: 'var(--neutral-100)'
|
|
63
|
+
# dark:
|
|
64
|
+
# heading: white
|
|
65
|
+
|
|
66
|
+
# ─── Inline Text Styles ───────────────────────────────────────────────────────
|
|
67
|
+
# Named styles for inline text in markdown: [text]{emphasis}
|
|
68
|
+
# Each name maps to CSS properties.
|
|
69
|
+
# Defaults: emphasis (colored + bold), muted (subtle).
|
|
70
|
+
|
|
71
|
+
# inline:
|
|
72
|
+
# emphasis:
|
|
73
|
+
# color: 'var(--link)'
|
|
74
|
+
# font-weight: '600'
|
|
75
|
+
# muted:
|
|
76
|
+
# color: 'var(--subtle)'
|
|
77
|
+
|
|
78
|
+
# ─── Code Blocks ───────────────────────────────────────────────────────────────
|
|
79
|
+
# Syntax highlighting colors for code blocks (uses Shiki).
|
|
80
|
+
|
|
81
|
+
# code:
|
|
82
|
+
# background: '#1e1e2e'
|
|
83
|
+
# foreground: '#cdd6f4'
|
|
84
|
+
# keyword: '#cba6f7'
|
|
85
|
+
# string: '#a6e3a1'
|
|
86
|
+
# number: '#fab387'
|
|
87
|
+
# comment: '#6c7086'
|
|
88
|
+
# function: '#89b4fa'
|
|
89
|
+
# variable: '#f5e0dc'
|
|
90
|
+
# type: '#f9e2af'
|
|
91
|
+
# property: '#94e2d5'
|
|
92
|
+
# tag: '#89b4fa'
|
|
93
|
+
|
|
94
|
+
# ─── Foundation Variables ──────────────────────────────────────────────────────
|
|
95
|
+
# Override variables declared by the foundation (e.g., header-height, max-width).
|
|
96
|
+
# Available variables depend on the foundation — check its foundation.js.
|
|
97
|
+
|
|
98
|
+
# vars:
|
|
99
|
+
# header-height: 5rem
|