uniweb 0.12.9 → 0.12.11
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 +13 -5
- package/package.json +5 -5
- package/partials/agents.md +22 -5
- package/src/commands/add.js +2 -2
- package/src/commands/build.js +22 -7
- package/src/commands/deploy.js +150 -30
- package/src/commands/dev.js +111 -0
- package/src/commands/export.js +19 -6
- package/src/commands/handoff.js +1 -1
- package/src/commands/invite.js +1 -1
- package/src/commands/publish.js +1 -1
- package/src/commands/template.js +1 -1
- package/src/framework-index.json +6 -6
- package/src/index.js +362 -12
- package/src/utils/args.js +37 -0
- package/src/utils/auth.js +29 -1
- package/src/utils/config.js +15 -6
- package/src/utils/host-prompt.js +50 -0
- package/src/utils/update-check.js +34 -3
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-05T20:43:37.314Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.14.
|
|
6
|
+
"version": "0.14.2",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"deps": []
|
|
25
25
|
},
|
|
26
26
|
"@uniweb/core": {
|
|
27
|
-
"version": "0.7.
|
|
27
|
+
"version": "0.7.11",
|
|
28
28
|
"path": "framework/core",
|
|
29
29
|
"deps": [
|
|
30
30
|
"@uniweb/semantic-parser",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"deps": []
|
|
43
43
|
},
|
|
44
44
|
"@uniweb/kit": {
|
|
45
|
-
"version": "0.9.
|
|
45
|
+
"version": "0.9.11",
|
|
46
46
|
"path": "framework/kit",
|
|
47
47
|
"deps": [
|
|
48
48
|
"@uniweb/core"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"deps": []
|
|
60
60
|
},
|
|
61
61
|
"@uniweb/runtime": {
|
|
62
|
-
"version": "0.8.
|
|
62
|
+
"version": "0.8.13",
|
|
63
63
|
"path": "framework/runtime",
|
|
64
64
|
"deps": [
|
|
65
65
|
"@uniweb/core",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"deps": []
|
|
93
93
|
},
|
|
94
94
|
"@uniweb/unipress": {
|
|
95
|
-
"version": "0.4.
|
|
95
|
+
"version": "0.4.7",
|
|
96
96
|
"path": "framework/unipress",
|
|
97
97
|
"deps": [
|
|
98
98
|
"@uniweb/build",
|
package/src/index.js
CHANGED
|
@@ -199,12 +199,16 @@ async function createFromPackageTemplates(projectDir, projectName, options = {})
|
|
|
199
199
|
|
|
200
200
|
onProgress?.('Setting up workspace...')
|
|
201
201
|
|
|
202
|
-
// 1. Scaffold workspace
|
|
202
|
+
// 1. Scaffold workspace.
|
|
203
|
+
// dev/build go through `uniweb` verbs so the scripts stay PM-agnostic
|
|
204
|
+
// (the verb resolves the right PM at runtime instead of locking the
|
|
205
|
+
// root scripts to whichever PM ran `npx uniweb create`). preview stays
|
|
206
|
+
// PM-filtered until a `uniweb preview` verb exists.
|
|
203
207
|
await scaffoldWorkspace(projectDir, {
|
|
204
208
|
projectName,
|
|
205
209
|
workspaceGlobs: ['site', 'src'],
|
|
206
210
|
scripts: {
|
|
207
|
-
dev:
|
|
211
|
+
dev: 'uniweb dev',
|
|
208
212
|
build: 'uniweb build',
|
|
209
213
|
preview: filterCmd(pm, 'site', 'preview'),
|
|
210
214
|
},
|
|
@@ -286,17 +290,19 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
286
290
|
const scripts = {
|
|
287
291
|
build: 'uniweb build',
|
|
288
292
|
}
|
|
293
|
+
// dev goes through `uniweb` (PM-agnostic; see computeRootScripts).
|
|
294
|
+
// preview stays PM-filtered until a `uniweb preview` verb exists.
|
|
289
295
|
if (sites.length === 1) {
|
|
290
|
-
scripts.dev =
|
|
296
|
+
scripts.dev = 'uniweb dev'
|
|
291
297
|
scripts.preview = filterCmd(pm, sites[0].name, 'preview')
|
|
292
298
|
} else {
|
|
293
299
|
for (const s of sites) {
|
|
294
|
-
scripts[`dev:${s.name}`] =
|
|
300
|
+
scripts[`dev:${s.name}`] = `uniweb dev ${s.name}`
|
|
295
301
|
scripts[`preview:${s.name}`] = filterCmd(pm, s.name, 'preview')
|
|
296
302
|
}
|
|
297
303
|
// First site gets unqualified aliases
|
|
298
304
|
if (sites.length > 0) {
|
|
299
|
-
scripts.dev =
|
|
305
|
+
scripts.dev = 'uniweb dev'
|
|
300
306
|
scripts.preview = filterCmd(pm, sites[0].name, 'preview')
|
|
301
307
|
}
|
|
302
308
|
}
|
|
@@ -452,12 +458,26 @@ async function main() {
|
|
|
452
458
|
// Commands that need @uniweb/build will get a helpful error via importProjectCommand().
|
|
453
459
|
}
|
|
454
460
|
|
|
455
|
-
// Start non-blocking update check for global installs
|
|
461
|
+
// Start non-blocking update check for global installs.
|
|
462
|
+
//
|
|
463
|
+
// Two surfaces:
|
|
464
|
+
// - showUpdateNotification (soft, trailing): printed at command end for
|
|
465
|
+
// any verb. Doesn't interrupt the user's workflow.
|
|
466
|
+
// - eager (loud, leading): printed BEFORE staleness-sensitive verbs do
|
|
467
|
+
// their work. Today: only `create` (templates ship with the CLI, so
|
|
468
|
+
// a stale CLI scaffolds stale starter content; the user needs to know
|
|
469
|
+
// before files hit disk). Other verbs are insensitive — `deploy` etc.
|
|
470
|
+
// are project-bound (delegated to local node_modules), and the
|
|
471
|
+
// local-vs-global mismatch warning in delegateToLocal already covers
|
|
472
|
+
// that case.
|
|
456
473
|
let showUpdateNotification = () => {}
|
|
457
474
|
if (global) {
|
|
458
475
|
try {
|
|
459
|
-
const { startUpdateCheck } = await import('./utils/update-check.js')
|
|
476
|
+
const { startUpdateCheck, maybeEagerNotification } = await import('./utils/update-check.js')
|
|
460
477
|
showUpdateNotification = startUpdateCheck(getCliVersion())
|
|
478
|
+
if (command === 'create') {
|
|
479
|
+
maybeEagerNotification(getCliVersion())
|
|
480
|
+
}
|
|
461
481
|
} catch {
|
|
462
482
|
// Update check is optional — don't fail if the module is missing
|
|
463
483
|
}
|
|
@@ -470,6 +490,23 @@ async function main() {
|
|
|
470
490
|
return
|
|
471
491
|
}
|
|
472
492
|
|
|
493
|
+
// Per-command --help: short-circuit BEFORE the command's side effects run.
|
|
494
|
+
// Critical for `deploy --help` (used to open a browser to production for
|
|
495
|
+
// login because deploy.js doesn't parse --help and ensureAuth ran first).
|
|
496
|
+
// Falls back to the global help when a command has no dedicated block.
|
|
497
|
+
if (args.slice(1).some(a => a === '--help' || a === '-h')) {
|
|
498
|
+
const printed = printCommandHelp(command)
|
|
499
|
+
if (printed) {
|
|
500
|
+
await showUpdateNotification()
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
// No dedicated block — show global help as a useful fallback rather
|
|
504
|
+
// than executing the command (which often has side effects).
|
|
505
|
+
showHelp()
|
|
506
|
+
await showUpdateNotification()
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
473
510
|
// Handle build command (dynamic import — depends on @uniweb/build)
|
|
474
511
|
if (command === 'build') {
|
|
475
512
|
const { build } = await importProjectCommand('./commands/build.js')
|
|
@@ -478,6 +515,16 @@ async function main() {
|
|
|
478
515
|
return
|
|
479
516
|
}
|
|
480
517
|
|
|
518
|
+
// Handle dev command — thin wrapper that shells to the package manager's
|
|
519
|
+
// workspace-filtered `dev` script (mirrors what `uniweb create` writes
|
|
520
|
+
// into the root package.json::scripts.dev). Lazy import keeps startup
|
|
521
|
+
// fast when the user is not running dev.
|
|
522
|
+
if (command === 'dev') {
|
|
523
|
+
const { dev } = await import('./commands/dev.js')
|
|
524
|
+
await dev(args.slice(1))
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
|
|
481
528
|
// Handle docs command (dynamic import — depends on @uniweb/build)
|
|
482
529
|
if (command === 'docs') {
|
|
483
530
|
const { docs } = await importProjectCommand('./commands/docs.js')
|
|
@@ -807,18 +854,307 @@ async function main() {
|
|
|
807
854
|
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
808
855
|
log(` ${colors.cyan}${prefix} add project${colors.reset}`)
|
|
809
856
|
log(` ${colors.cyan}${installCmd(pm)}${colors.reset}`)
|
|
810
|
-
log(` ${colors.cyan}${
|
|
857
|
+
log(` ${colors.cyan}${prefix} dev${colors.reset} ${colors.dim}# Start dev server${colors.reset}`)
|
|
811
858
|
} else {
|
|
812
859
|
log(`Next steps:\n`)
|
|
813
860
|
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
814
861
|
log(` ${colors.cyan}${installCmd(pm)}${colors.reset}`)
|
|
815
|
-
log(` ${colors.cyan}${
|
|
862
|
+
log(` ${colors.cyan}${prefix} dev${colors.reset} ${colors.dim}# Start dev server${colors.reset}`)
|
|
816
863
|
}
|
|
817
864
|
log('')
|
|
865
|
+
log(`When ready to ship:\n`)
|
|
866
|
+
log(` ${colors.cyan}${prefix} deploy${colors.reset} ${colors.dim}# Uniweb hosting (default; uniweb login first)${colors.reset}`)
|
|
867
|
+
log(` ${colors.cyan}${prefix} deploy --host=<adapter>${colors.reset} ${colors.dim}# cloudflare-pages, netlify, vercel, github-pages, s3-cloudfront${colors.reset}`)
|
|
868
|
+
log(` ${colors.cyan}${prefix} export${colors.reset} ${colors.dim}# Build dist/ for any static host (no Uniweb account)${colors.reset}`)
|
|
869
|
+
log('')
|
|
870
|
+
log(` ${colors.dim}See ${colors.reset}${colors.cyan}${prefix} <command> --help${colors.reset}${colors.dim} for command-specific options.${colors.reset}`)
|
|
871
|
+
log('')
|
|
818
872
|
|
|
819
873
|
await showUpdateNotification()
|
|
820
874
|
}
|
|
821
875
|
|
|
876
|
+
/**
|
|
877
|
+
* Print help for a specific command. Returns true if a dedicated help
|
|
878
|
+
* block exists for the command, false to signal "fall back to global
|
|
879
|
+
* help."
|
|
880
|
+
*
|
|
881
|
+
* Help text intentionally lives next to the dispatcher rather than in
|
|
882
|
+
* the per-command files because most help-seekers haven't run that
|
|
883
|
+
* command yet — keeping it here means `uniweb foo --help` prints
|
|
884
|
+
* without loading @uniweb/build or any project context.
|
|
885
|
+
*/
|
|
886
|
+
function printCommandHelp(command) {
|
|
887
|
+
const blocks = {
|
|
888
|
+
deploy: `
|
|
889
|
+
${colors.cyan}${colors.bright}uniweb deploy${colors.reset} ${colors.dim}— Deploy a site${colors.reset}
|
|
890
|
+
|
|
891
|
+
${colors.bright}Usage:${colors.reset}
|
|
892
|
+
uniweb deploy [options]
|
|
893
|
+
|
|
894
|
+
The host is determined by the resolved deploy.yml target. Defaults to
|
|
895
|
+
${colors.cyan}uniweb${colors.reset} hosting (link-mode, edge JIT prerender) when no deploy.yml exists.
|
|
896
|
+
|
|
897
|
+
${colors.bright}Hosts:${colors.reset}
|
|
898
|
+
uniweb Uniweb hosting (default; requires \`uniweb login\`)
|
|
899
|
+
cloudflare-pages Cloudflare Pages (build artifact + adapter postBuild)
|
|
900
|
+
netlify Netlify (alias of cloudflare-pages adapter)
|
|
901
|
+
vercel Vercel (build-only — deploy via \`npx vercel\`)
|
|
902
|
+
github-pages GitHub Pages (build-only — push dist/ to gh-pages)
|
|
903
|
+
s3-cloudfront AWS S3 + CloudFront (uploads + invalidates via CLI)
|
|
904
|
+
generic-static Plain static-host build, no host-specific helpers
|
|
905
|
+
|
|
906
|
+
${colors.bright}Options:${colors.reset}
|
|
907
|
+
--target <name> Pick a target from deploy.yml (default: deploy.yml's \`default:\`)
|
|
908
|
+
--host <name> Override the resolved target's host (does not persist)
|
|
909
|
+
--host No value → interactive picker (TTY only)
|
|
910
|
+
--dry-run Resolve site.yml + foundation/runtime; print summary; no writes
|
|
911
|
+
--no-auto-publish Don't auto-publish workspace-local foundation as part of deploy
|
|
912
|
+
--no-save Skip the auto-save of lastDeploy in deploy.yml
|
|
913
|
+
--local Internal: target the unicloud mock (see workspace root CLAUDE.md)
|
|
914
|
+
--non-interactive Fail with usage info instead of prompting
|
|
915
|
+
|
|
916
|
+
${colors.bright}Auth:${colors.reset}
|
|
917
|
+
\`host: uniweb\` requires authentication. Run \`uniweb login\` first, set
|
|
918
|
+
\`UNIWEB_TOKEN=<bearer>\` env var, or use a static-host adapter that
|
|
919
|
+
doesn't need a Uniweb account. CI / agents / piped stdin auto-detect
|
|
920
|
+
non-interactive mode and bail with an actionable error instead of
|
|
921
|
+
hanging on a browser callback.
|
|
922
|
+
|
|
923
|
+
${colors.bright}Examples:${colors.reset}
|
|
924
|
+
uniweb deploy # Default (host=uniweb)
|
|
925
|
+
uniweb deploy --dry-run # Print summary, no writes
|
|
926
|
+
uniweb deploy --host=cloudflare-pages # One-off override
|
|
927
|
+
uniweb deploy --target=preview # Pick named target from deploy.yml
|
|
928
|
+
`,
|
|
929
|
+
publish: `
|
|
930
|
+
${colors.cyan}${colors.bright}uniweb publish${colors.reset} ${colors.dim}— Publish a foundation to the catalog${colors.reset}
|
|
931
|
+
|
|
932
|
+
${colors.bright}Usage:${colors.reset}
|
|
933
|
+
uniweb publish [@org/name] [options]
|
|
934
|
+
|
|
935
|
+
For site-bound foundations (one foundation, one site), use \`uniweb deploy\`
|
|
936
|
+
instead — it auto-publishes under a site-scoped slot, no naming ceremony.
|
|
937
|
+
|
|
938
|
+
${colors.bright}Options:${colors.reset}
|
|
939
|
+
--catalog Confirm publish to the public catalog (required in CI)
|
|
940
|
+
--propagate Walk trusting sites' policy waves (default: silent)
|
|
941
|
+
--name <id> Foundation id (overrides package.json::uniweb.id)
|
|
942
|
+
--namespace <ns> Force org-scope namespace (overrides package.json)
|
|
943
|
+
--local Internal: publish to the unicloud mock (see workspace root CLAUDE.md)
|
|
944
|
+
--registry <url> Use a specific registry URL
|
|
945
|
+
--edit-access <p> "open" or "restricted" (default: restricted)
|
|
946
|
+
--dry-run Show what would be published without uploading
|
|
947
|
+
--non-interactive Fail with usage info instead of prompting
|
|
948
|
+
`,
|
|
949
|
+
create: `
|
|
950
|
+
${colors.cyan}${colors.bright}uniweb create${colors.reset} ${colors.dim}— Create a new project${colors.reset}
|
|
951
|
+
|
|
952
|
+
${colors.bright}Usage:${colors.reset}
|
|
953
|
+
uniweb create [name] [options]
|
|
954
|
+
|
|
955
|
+
${colors.bright}Options:${colors.reset}
|
|
956
|
+
--template <type> Project template (default: starter)
|
|
957
|
+
Built-in: starter, none, marketing
|
|
958
|
+
Local: ./path/to/template
|
|
959
|
+
npm: @scope/template-name
|
|
960
|
+
GitHub: github:user/repo or https://github.com/user/repo
|
|
961
|
+
--blank Create an empty workspace (grow with \`uniweb add\`)
|
|
962
|
+
--name <name> Project display name
|
|
963
|
+
--no-git Skip git repository initialization
|
|
964
|
+
|
|
965
|
+
${colors.bright}Examples:${colors.reset}
|
|
966
|
+
uniweb create my-project # Foundation + site + starter content
|
|
967
|
+
uniweb create my-project --template marketing # Official template
|
|
968
|
+
uniweb create my-project --blank # Empty workspace
|
|
969
|
+
`,
|
|
970
|
+
dev: `
|
|
971
|
+
${colors.cyan}${colors.bright}uniweb dev${colors.reset} ${colors.dim}— Start a dev server for a site${colors.reset}
|
|
972
|
+
|
|
973
|
+
${colors.bright}Usage:${colors.reset}
|
|
974
|
+
uniweb dev Start dev server for the (single) site
|
|
975
|
+
uniweb dev <site> Start dev server for a specific site
|
|
976
|
+
uniweb dev --site <name> Same, with explicit flag form
|
|
977
|
+
|
|
978
|
+
Thin wrapper around the package manager's workspace-filtered \`dev\`
|
|
979
|
+
script (\`pnpm --filter <site> dev\` or \`npm -w <site> run dev\`). Picks
|
|
980
|
+
the single site automatically; for multi-site workspaces the first
|
|
981
|
+
site runs by default with a notice pointing at \`--site\` for explicit
|
|
982
|
+
selection.
|
|
983
|
+
`,
|
|
984
|
+
build: `
|
|
985
|
+
${colors.cyan}${colors.bright}uniweb build${colors.reset} ${colors.dim}— Build the current project${colors.reset}
|
|
986
|
+
|
|
987
|
+
${colors.bright}Usage:${colors.reset}
|
|
988
|
+
uniweb build [options]
|
|
989
|
+
|
|
990
|
+
At workspace root, builds all foundations first, then all sites.
|
|
991
|
+
Pre-rendering is enabled by default when build.prerender: true in site.yml.
|
|
992
|
+
|
|
993
|
+
${colors.bright}Options:${colors.reset}
|
|
994
|
+
--target <type> Build target (foundation, site) — auto-detected if not specified
|
|
995
|
+
--prerender Force pre-rendering (overrides site.yml)
|
|
996
|
+
--no-prerender Skip pre-rendering (overrides site.yml)
|
|
997
|
+
--foundation-dir Path to foundation directory (for prerendering)
|
|
998
|
+
--host <name> Apply host-specific postBuild (e.g., cloudflare-pages emits _redirects)
|
|
999
|
+
--platform <name> (Deprecated alias for --host)
|
|
1000
|
+
`,
|
|
1001
|
+
add: `
|
|
1002
|
+
${colors.cyan}${colors.bright}uniweb add${colors.reset} ${colors.dim}— Add a foundation, site, or extension${colors.reset}
|
|
1003
|
+
|
|
1004
|
+
${colors.bright}Subcommands:${colors.reset}
|
|
1005
|
+
add project [name] Add a co-located foundation + site pair
|
|
1006
|
+
add foundation [name] Add a foundation (--from, --path, --project)
|
|
1007
|
+
add site [name] Add a site (--from, --foundation, --path, --project)
|
|
1008
|
+
add extension <name> Add an extension (--from, --site, --path)
|
|
1009
|
+
add section <name> Add a section type to a foundation (--foundation)
|
|
1010
|
+
|
|
1011
|
+
${colors.bright}Common options:${colors.reset}
|
|
1012
|
+
--from <template> Source content from a template
|
|
1013
|
+
--path <dir> Override default folder location
|
|
1014
|
+
--foundation <name> Wire site/extension to this foundation (CI-friendly)
|
|
1015
|
+
--site <name> Wire extension to this site (CI-friendly)
|
|
1016
|
+
--non-interactive Fail with usage info instead of prompting
|
|
1017
|
+
`,
|
|
1018
|
+
export: `
|
|
1019
|
+
${colors.cyan}${colors.bright}uniweb export${colors.reset} ${colors.dim}— Export a self-contained site for third-party hosting${colors.reset}
|
|
1020
|
+
|
|
1021
|
+
${colors.bright}Usage:${colors.reset}
|
|
1022
|
+
uniweb export [options]
|
|
1023
|
+
|
|
1024
|
+
Builds dist/ and prints upload examples for common static hosts. No login,
|
|
1025
|
+
no deploy step — you push the artifact to your host of choice yourself.
|
|
1026
|
+
For Uniweb-hosted sites, use \`uniweb deploy\`.
|
|
1027
|
+
|
|
1028
|
+
${colors.bright}Options:${colors.reset}
|
|
1029
|
+
--no-prerender Skip per-page prerendered HTML
|
|
1030
|
+
--host <name> Apply host-specific postBuild (cloudflare-pages, github-pages, …)
|
|
1031
|
+
`,
|
|
1032
|
+
doctor: `
|
|
1033
|
+
${colors.cyan}${colors.bright}uniweb doctor${colors.reset} ${colors.dim}— Diagnose project configuration issues${colors.reset}
|
|
1034
|
+
|
|
1035
|
+
${colors.bright}Usage:${colors.reset}
|
|
1036
|
+
uniweb doctor [options]
|
|
1037
|
+
|
|
1038
|
+
${colors.bright}Options:${colors.reset}
|
|
1039
|
+
--fix Apply fixes for safely-fixable issues
|
|
1040
|
+
--fix <issue-id> Apply fix for a specific issue id only
|
|
1041
|
+
--non-interactive Fail with usage info instead of prompting
|
|
1042
|
+
|
|
1043
|
+
Exit code is 1 if errors are found (warnings only → exit 0).
|
|
1044
|
+
`,
|
|
1045
|
+
rename: `
|
|
1046
|
+
${colors.cyan}${colors.bright}uniweb rename${colors.reset} ${colors.dim}— Rename a workspace package${colors.reset}
|
|
1047
|
+
|
|
1048
|
+
${colors.bright}Usage:${colors.reset}
|
|
1049
|
+
uniweb rename foundation <old> <new>
|
|
1050
|
+
|
|
1051
|
+
Today supports renaming foundations only. Updates folder name, foundation
|
|
1052
|
+
package.json::name, every dependent site's site.yml::foundation, every
|
|
1053
|
+
dependent site's package.json::dependencies, pnpm-workspace.yaml, and
|
|
1054
|
+
package.json::workspaces. Transactional — bails on conflict before any
|
|
1055
|
+
filesystem mutation.
|
|
1056
|
+
`,
|
|
1057
|
+
login: `
|
|
1058
|
+
${colors.cyan}${colors.bright}uniweb login${colors.reset} ${colors.dim}— Log in to your Uniweb account${colors.reset}
|
|
1059
|
+
|
|
1060
|
+
${colors.bright}Usage:${colors.reset}
|
|
1061
|
+
uniweb login [options]
|
|
1062
|
+
|
|
1063
|
+
Opens a browser to hub.uniweb.app for OAuth-style login, then captures
|
|
1064
|
+
the token via a loopback callback. Falls back to a paste-token prompt
|
|
1065
|
+
if the browser flow fails.
|
|
1066
|
+
|
|
1067
|
+
${colors.bright}Options:${colors.reset}
|
|
1068
|
+
--backend <url> Override the auth backend (default: https://hub.uniweb.app)
|
|
1069
|
+
|
|
1070
|
+
In non-interactive mode (CI / no TTY / --non-interactive), this command
|
|
1071
|
+
errors out — set the \`UNIWEB_TOKEN\` env var instead, or run \`login\`
|
|
1072
|
+
once on a machine with a browser to seed ~/.uniweb/auth.json.
|
|
1073
|
+
`,
|
|
1074
|
+
invite: `
|
|
1075
|
+
${colors.cyan}${colors.bright}uniweb invite${colors.reset} ${colors.dim}— Create a foundation invite for a client${colors.reset}
|
|
1076
|
+
|
|
1077
|
+
${colors.bright}Usage:${colors.reset}
|
|
1078
|
+
uniweb invite <email> [options]
|
|
1079
|
+
|
|
1080
|
+
${colors.bright}Options:${colors.reset}
|
|
1081
|
+
--uses <n> Max sites per invite (default: 1)
|
|
1082
|
+
--expires <days> Days until expiry (default: 30)
|
|
1083
|
+
--version <n> Major version to license (default: current)
|
|
1084
|
+
--list List invites for your foundation
|
|
1085
|
+
--revoke <id> Revoke an invite
|
|
1086
|
+
--resend <id> Resend an invite
|
|
1087
|
+
`,
|
|
1088
|
+
handoff: `
|
|
1089
|
+
${colors.cyan}${colors.bright}uniweb handoff${colors.reset} ${colors.dim}— Hand off a site to a client${colors.reset}
|
|
1090
|
+
|
|
1091
|
+
${colors.bright}Usage:${colors.reset}
|
|
1092
|
+
uniweb handoff <email> [options]
|
|
1093
|
+
|
|
1094
|
+
${colors.bright}Options:${colors.reset}
|
|
1095
|
+
--site <id> Site identifier (default: auto-generated)
|
|
1096
|
+
--web Show web-based handoff instructions instead
|
|
1097
|
+
`,
|
|
1098
|
+
template: `
|
|
1099
|
+
${colors.cyan}${colors.bright}uniweb template${colors.reset} ${colors.dim}— Manage cloud templates${colors.reset}
|
|
1100
|
+
|
|
1101
|
+
${colors.bright}Subcommands:${colors.reset}
|
|
1102
|
+
template publish Publish a site as a cloud template
|
|
1103
|
+
|
|
1104
|
+
${colors.bright}Publish Options:${colors.reset}
|
|
1105
|
+
--name <name> Template registry name (overrides site.yml template: field)
|
|
1106
|
+
--title <title> Display title (overrides site.yml name: field)
|
|
1107
|
+
--description <t> Description
|
|
1108
|
+
--registry <url> Registry URL (default: http://localhost:4001)
|
|
1109
|
+
`,
|
|
1110
|
+
docs: `
|
|
1111
|
+
${colors.cyan}${colors.bright}uniweb docs${colors.reset} ${colors.dim}— Generate component documentation${colors.reset}
|
|
1112
|
+
|
|
1113
|
+
${colors.bright}Subcommands:${colors.reset}
|
|
1114
|
+
docs Generate COMPONENTS.md from foundation schema
|
|
1115
|
+
docs site Show site.yml configuration reference
|
|
1116
|
+
docs page Show page.yml configuration reference
|
|
1117
|
+
docs meta Show component meta.js reference
|
|
1118
|
+
|
|
1119
|
+
${colors.bright}Options:${colors.reset}
|
|
1120
|
+
--output <file> Output filename (default: COMPONENTS.md)
|
|
1121
|
+
--from-source Read meta.js files directly instead of schema.json
|
|
1122
|
+
`,
|
|
1123
|
+
i18n: `
|
|
1124
|
+
${colors.cyan}${colors.bright}uniweb i18n${colors.reset} ${colors.dim}— Internationalization workflow${colors.reset}
|
|
1125
|
+
|
|
1126
|
+
${colors.bright}Subcommands:${colors.reset}
|
|
1127
|
+
i18n extract Extract translatable strings to manifest
|
|
1128
|
+
i18n sync Update manifest with content changes
|
|
1129
|
+
i18n status Show translation coverage per locale
|
|
1130
|
+
`,
|
|
1131
|
+
inspect: `
|
|
1132
|
+
${colors.cyan}${colors.bright}uniweb inspect${colors.reset} ${colors.dim}— Inspect parsed content shape${colors.reset}
|
|
1133
|
+
|
|
1134
|
+
${colors.bright}Usage:${colors.reset}
|
|
1135
|
+
uniweb inspect <path>
|
|
1136
|
+
|
|
1137
|
+
Prints the parsed content shape of a markdown file or folder — the
|
|
1138
|
+
{ content, params, items, … } object that components actually receive.
|
|
1139
|
+
Useful for debugging "why isn't my section getting X?".
|
|
1140
|
+
`,
|
|
1141
|
+
update: `
|
|
1142
|
+
${colors.cyan}${colors.bright}uniweb update${colors.reset} ${colors.dim}— Update AGENTS.md to match installed CLI version${colors.reset}
|
|
1143
|
+
|
|
1144
|
+
${colors.bright}Usage:${colors.reset}
|
|
1145
|
+
uniweb update
|
|
1146
|
+
|
|
1147
|
+
Refreshes the project's AGENTS.md from the CLI's bundled version. Run
|
|
1148
|
+
after upgrading the \`uniweb\` package to pick up new content authoring
|
|
1149
|
+
patterns and platform documentation.
|
|
1150
|
+
`,
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (!blocks[command]) return false
|
|
1154
|
+
log(blocks[command])
|
|
1155
|
+
return true
|
|
1156
|
+
}
|
|
1157
|
+
|
|
822
1158
|
function showHelp() {
|
|
823
1159
|
log(`
|
|
824
1160
|
${colors.cyan}${colors.bright}Uniweb CLI${colors.reset} ${colors.dim}v${getCliVersion()}${colors.reset}
|
|
@@ -830,6 +1166,7 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
830
1166
|
create [name] Create a new project
|
|
831
1167
|
add <type> [name] Add a foundation, site, or extension to a project
|
|
832
1168
|
rename <type> Rename a workspace package (foundation today)
|
|
1169
|
+
dev Start a dev server for a site
|
|
833
1170
|
build Build the current project
|
|
834
1171
|
deploy Deploy a site to Uniweb hosting
|
|
835
1172
|
export Export a self-contained site for third-party hosting
|
|
@@ -896,21 +1233,34 @@ ${colors.bright}Template Options:${colors.reset}
|
|
|
896
1233
|
--registry <url> Registry URL (default: http://localhost:4001)
|
|
897
1234
|
|
|
898
1235
|
${colors.bright}Deploy Options:${colors.reset}
|
|
1236
|
+
--target <name> Pick a target from deploy.yml (default: deploy.yml's \`default:\`)
|
|
1237
|
+
--host <name> Override the resolved target's host (does not persist).
|
|
1238
|
+
Without a value, opens an interactive picker (TTY only).
|
|
1239
|
+
Hosts: uniweb, cloudflare-pages, netlify, vercel,
|
|
1240
|
+
github-pages, s3-cloudfront, generic-static.
|
|
899
1241
|
--dry-run Resolve site.yml + foundation/runtime; print summary; no writes
|
|
900
1242
|
--no-auto-publish Don't auto-publish workspace-local foundation as part of deploy
|
|
1243
|
+
--no-save Skip the auto-save of lastDeploy in deploy.yml
|
|
1244
|
+
|
|
1245
|
+
${colors.bright}Dev Options:${colors.reset}
|
|
1246
|
+
<site> Site name to run (positional)
|
|
1247
|
+
--site <name> Site name to run (explicit form)
|
|
901
1248
|
|
|
902
1249
|
${colors.bright}Export Options:${colors.reset}
|
|
903
1250
|
--no-prerender Skip per-page prerendered HTML
|
|
1251
|
+
--host <name> Apply host-specific postBuild (cloudflare-pages, github-pages, …)
|
|
904
1252
|
|
|
905
1253
|
${colors.bright}Build Options:${colors.reset}
|
|
906
|
-
--target <type> Build target (foundation, site)
|
|
1254
|
+
--target <type> Build target (foundation, site) — auto-detected if not specified
|
|
907
1255
|
--prerender Force pre-rendering (overrides site.yml)
|
|
908
1256
|
--no-prerender Skip pre-rendering (overrides site.yml)
|
|
909
1257
|
--foundation-dir Path to foundation directory (for prerendering)
|
|
910
|
-
--
|
|
1258
|
+
--host <name> Apply host-specific postBuild (cloudflare-pages, s3-cloudfront, …)
|
|
1259
|
+
--platform <name> (Deprecated alias for --host)
|
|
911
1260
|
|
|
912
1261
|
At workspace root, builds all foundations first, then all sites.
|
|
913
|
-
Pre-rendering is enabled by default when build.prerender: true in site.yml
|
|
1262
|
+
Pre-rendering is enabled by default when build.prerender: true in site.yml.
|
|
1263
|
+
See \`uniweb <command> --help\` for command-specific detail and examples.
|
|
914
1264
|
|
|
915
1265
|
${colors.bright}Docs Subcommands:${colors.reset}
|
|
916
1266
|
docs Generate COMPONENTS.md from foundation schema
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* argv parsing helpers shared across CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read `--flag value` from argv. Accepts both `--flag value` and
|
|
7
|
+
* `--flag=value`.
|
|
8
|
+
*
|
|
9
|
+
* Returns:
|
|
10
|
+
* - undefined when the flag is absent
|
|
11
|
+
* - null when the flag is present without a value (last arg, next is
|
|
12
|
+
* another flag, or `--flag=` empty form)
|
|
13
|
+
* - string when the flag carries a value
|
|
14
|
+
*
|
|
15
|
+
* The three-state return lets callers distinguish "not given" (e.g.,
|
|
16
|
+
* fall back to a default) from "given but empty" (e.g., trigger an
|
|
17
|
+
* interactive prompt).
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} args
|
|
20
|
+
* @param {string} name — Including the leading dashes, e.g. '--host'.
|
|
21
|
+
* @returns {string | null | undefined}
|
|
22
|
+
*/
|
|
23
|
+
export function readFlagValue(args, name) {
|
|
24
|
+
const eqPrefix = name + '='
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
if (args[i] === name) {
|
|
27
|
+
const next = args[i + 1]
|
|
28
|
+
if (next === undefined || next.startsWith('--')) return null
|
|
29
|
+
return next
|
|
30
|
+
}
|
|
31
|
+
if (args[i].startsWith(eqPrefix)) {
|
|
32
|
+
const v = args[i].slice(eqPrefix.length)
|
|
33
|
+
return v === '' ? null : v
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
package/src/utils/auth.js
CHANGED
|
@@ -153,17 +153,45 @@ export function isExpired(auth) {
|
|
|
153
153
|
* Ensure the user is authenticated. If not, prompt inline login.
|
|
154
154
|
* Returns the auth token on success, exits the process on cancel.
|
|
155
155
|
*
|
|
156
|
+
* In non-interactive mode (CI, no TTY, or --non-interactive in args),
|
|
157
|
+
* bails with an actionable error instead of opening a browser. The browser
|
|
158
|
+
* login flow waits 120 seconds for a callback that can never arrive without
|
|
159
|
+
* a user, then drops to a token-paste prompt that pipes can't answer —
|
|
160
|
+
* silently burning two minutes per invocation. CI / agent / piped callers
|
|
161
|
+
* must set `UNIWEB_TOKEN`, run `uniweb login` interactively first, or use
|
|
162
|
+
* `--local` for the unicloud mock (see workspace root CLAUDE.md).
|
|
163
|
+
*
|
|
156
164
|
* @param {Object} options
|
|
157
165
|
* @param {string} options.command - The command that needs auth (for messaging)
|
|
166
|
+
* @param {string[]} [options.args] - Argv slice; checked for --non-interactive
|
|
158
167
|
* @returns {Promise<string>} Bearer token
|
|
159
168
|
*/
|
|
160
|
-
export async function ensureAuth({ command = 'This command' } = {}) {
|
|
169
|
+
export async function ensureAuth({ command = 'This command', args = [] } = {}) {
|
|
170
|
+
// Honor explicit token from env — useful for CI and agents.
|
|
171
|
+
if (process.env.UNIWEB_TOKEN) {
|
|
172
|
+
return process.env.UNIWEB_TOKEN
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
const auth = await readAuth()
|
|
162
176
|
|
|
163
177
|
if (auth?.token && !isExpired(auth)) {
|
|
164
178
|
return auth.token
|
|
165
179
|
}
|
|
166
180
|
|
|
181
|
+
// Non-interactive bail: don't open a browser, don't wait 120s, don't
|
|
182
|
+
// prompt for a token paste. Print an actionable error and exit.
|
|
183
|
+
const { isNonInteractive, getCliPrefix } = await import('./interactive.js')
|
|
184
|
+
if (isNonInteractive(args)) {
|
|
185
|
+
const prefix = getCliPrefix()
|
|
186
|
+
const reason = auth && isExpired(auth) ? 'Session expired.' : 'Not logged in.'
|
|
187
|
+
console.error(`\x1b[31m✗\x1b[0m ${reason} ${command} requires a Uniweb account, and the CLI is in non-interactive mode (CI / no TTY / --non-interactive).`)
|
|
188
|
+
console.error(` Options:`)
|
|
189
|
+
console.error(` • Run \`${prefix} login\` interactively first, then re-run.`)
|
|
190
|
+
console.error(` • Set the \`UNIWEB_TOKEN\` env var to a bearer token.`)
|
|
191
|
+
console.error(` • Use \`--local\` to target the unicloud mock (internal testing only — see workspace root CLAUDE.md).`)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
// Need to log in — delegate to the login command
|
|
168
196
|
if (auth && isExpired(auth)) {
|
|
169
197
|
console.log(`\x1b[33mSession expired.\x1b[0m ${command} requires a Uniweb account.\n`)
|
package/src/utils/config.js
CHANGED
|
@@ -157,9 +157,17 @@ export async function writeRootPackageJson(rootDir, pkg) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
* Compute root scripts based on discovered sites
|
|
160
|
+
* Compute root scripts based on discovered sites.
|
|
161
|
+
*
|
|
162
|
+
* `dev` and `build` route through the uniweb CLI verb (PM-agnostic — they
|
|
163
|
+
* resolve `uniweb` from the project's local node_modules/.bin, so `pnpm
|
|
164
|
+
* dev` and `npm run dev` both work without locking the scripts to one PM
|
|
165
|
+
* at create-time). `preview` stays on a PM-specific filter because there
|
|
166
|
+
* isn't a `uniweb preview` verb yet (the site's own `vite preview` is what
|
|
167
|
+
* runs); switch it over when one ships.
|
|
168
|
+
*
|
|
161
169
|
* @param {Array<{name: string, path: string}>} sites - Discovered sites
|
|
162
|
-
* @param {'pnpm' | 'npm'} [pm='pnpm'] - Package manager
|
|
170
|
+
* @param {'pnpm' | 'npm'} [pm='pnpm'] - Package manager (used only for preview)
|
|
163
171
|
* @returns {Object} Scripts object for package.json
|
|
164
172
|
*/
|
|
165
173
|
export function computeRootScripts(sites, pm = 'pnpm') {
|
|
@@ -172,16 +180,17 @@ export function computeRootScripts(sites, pm = 'pnpm') {
|
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
if (sites.length === 1) {
|
|
175
|
-
scripts.dev =
|
|
183
|
+
scripts.dev = 'uniweb dev'
|
|
176
184
|
scripts.preview = filterCmd(pm, sites[0].name, 'preview')
|
|
177
185
|
} else {
|
|
178
|
-
// First site gets unqualified dev/preview
|
|
179
|
-
|
|
186
|
+
// First site gets unqualified dev/preview (matches `uniweb dev`'s
|
|
187
|
+
// default-to-first-site behavior).
|
|
188
|
+
scripts.dev = 'uniweb dev'
|
|
180
189
|
scripts.preview = filterCmd(pm, sites[0].name, 'preview')
|
|
181
190
|
|
|
182
191
|
// Subsequent sites get qualified dev:{name}/preview:{name}
|
|
183
192
|
for (let i = 1; i < sites.length; i++) {
|
|
184
|
-
scripts[`dev:${sites[i].name}`] =
|
|
193
|
+
scripts[`dev:${sites[i].name}`] = `uniweb dev ${sites[i].name}`
|
|
185
194
|
scripts[`preview:${sites[i].name}`] = filterCmd(pm, sites[i].name, 'preview')
|
|
186
195
|
}
|
|
187
196
|
}
|